From 040bd7e406eb0d1d88c405b2d608088c9137cc7b Mon Sep 17 00:00:00 2001 From: Haiping Chen Date: Tue, 5 Nov 2024 13:29:06 +0000 Subject: [PATCH 01/29] ConnectToRedisAsync --- .../Infrastructures/DistributedLocker.cs | 24 +++++++++++++------ 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/src/Infrastructure/BotSharp.Core/Infrastructures/DistributedLocker.cs b/src/Infrastructure/BotSharp.Core/Infrastructures/DistributedLocker.cs index a5373cd1..25c3a047 100644 --- a/src/Infrastructure/BotSharp.Core/Infrastructures/DistributedLocker.cs +++ b/src/Infrastructure/BotSharp.Core/Infrastructures/DistributedLocker.cs @@ -15,7 +15,7 @@ public class DistributedLocker public async Task Lock(string resource, Func> action, int timeoutInSeconds = 30) { - await ConnectToRedis(); + await ConnectToRedisAsync(); var timeout = TimeSpan.FromSeconds(timeoutInSeconds); @@ -31,25 +31,35 @@ public class DistributedLocker } } - public async Task Lock(string resource, Action action, int timeoutInSeconds = 30) + public void Lock(string resource, Action action, int timeoutInSeconds = 30) { - await ConnectToRedis(); + ConnectToRedis(); var timeout = TimeSpan.FromSeconds(timeoutInSeconds); var @lock = new RedisDistributedLock(resource, connection.GetDatabase()); - await using (var handle = await @lock.TryAcquireAsync(timeout)) + using (var handle = @lock.TryAcquire(timeout)) { if (handle == null) { Serilog.Log.Logger.Error($"Acquire lock for {resource} failed due to after {timeout}s timeout."); - } - action(); + else + { + action(); + } } } - private async Task ConnectToRedis() + private void ConnectToRedis() + { + if (connection == null) + { + connection = ConnectionMultiplexer.Connect(_settings.Redis); + } + } + + private async Task ConnectToRedisAsync() { if (connection == null) { From d28aeaed79e1b8ec8a4fbb18cb0b748278df87cc Mon Sep 17 00:00:00 2001 From: Haiping Chen Date: Tue, 5 Nov 2024 23:37:21 -0600 Subject: [PATCH 02/29] WebDriver PressKey --- .../BotSharp.Abstraction/Browsing/IWebBrowser.cs | 1 + .../BotSharp.OpenAPI/BotSharpOpenApiExtensions.cs | 2 +- .../Drivers/PlaywrightDriver/PlaywrightWebDriver.cs | 10 ++++++++++ 3 files changed, 12 insertions(+), 1 deletion(-) diff --git a/src/Infrastructure/BotSharp.Abstraction/Browsing/IWebBrowser.cs b/src/Infrastructure/BotSharp.Abstraction/Browsing/IWebBrowser.cs index ea80e81a..ff2c4a7d 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Browsing/IWebBrowser.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Browsing/IWebBrowser.cs @@ -12,6 +12,7 @@ public interface IWebBrowser Task ActionOnElement(MessageInfo message, ElementLocatingArgs location, ElementActionArgs action); Task LocateElement(MessageInfo message, ElementLocatingArgs location); Task DoAction(MessageInfo message, ElementActionArgs action, BrowserActionResult result); + Task PressKey(MessageInfo message, string key); Task InputUserText(BrowserActionParams actionParams); Task InputUserPassword(BrowserActionParams actionParams); diff --git a/src/Infrastructure/BotSharp.OpenAPI/BotSharpOpenApiExtensions.cs b/src/Infrastructure/BotSharp.OpenAPI/BotSharpOpenApiExtensions.cs index 4d85167f..efad6e6c 100644 --- a/src/Infrastructure/BotSharp.OpenAPI/BotSharpOpenApiExtensions.cs +++ b/src/Infrastructure/BotSharp.OpenAPI/BotSharpOpenApiExtensions.cs @@ -209,7 +209,7 @@ public static class BotSharpOpenApiExtensions app.UseSwagger(); - if (env.IsDevelopment()) + // if (env.IsDevelopment()) { IdentityModelEventSource.ShowPII = true; app.UseSwaggerUI(); diff --git a/src/Plugins/BotSharp.Plugin.WebDriver/Drivers/PlaywrightDriver/PlaywrightWebDriver.cs b/src/Plugins/BotSharp.Plugin.WebDriver/Drivers/PlaywrightDriver/PlaywrightWebDriver.cs index 88a10485..4d13e26f 100644 --- a/src/Plugins/BotSharp.Plugin.WebDriver/Drivers/PlaywrightDriver/PlaywrightWebDriver.cs +++ b/src/Plugins/BotSharp.Plugin.WebDriver/Drivers/PlaywrightDriver/PlaywrightWebDriver.cs @@ -1,3 +1,4 @@ + namespace BotSharp.Plugin.WebDriver.Drivers.PlaywrightDriver; public partial class PlaywrightWebDriver : IWebBrowser @@ -68,4 +69,13 @@ public partial class PlaywrightWebDriver : IWebBrowser { _instance.SetServiceProvider(_services); } + + public async Task PressKey(MessageInfo message, string key) + { + var page = _instance.GetPage(message.ContextId); + if (page != null) + { + await page.Keyboard.PressAsync(key); + } + } } From 9844d9bfa2dec57fcec0c25629698bb701a50d1f Mon Sep 17 00:00:00 2001 From: Haiping Chen Date: Wed, 6 Nov 2024 23:21:51 -0600 Subject: [PATCH 03/29] Remove UseExistingPage and _activePage --- .../Browsing/Models/PageActionArgs.cs | 2 -- .../PlaywrightDriver/PlaywrightInstance.cs | 25 +------------------ .../PlaywrightWebDriver.GoToPage.cs | 15 ++--------- 3 files changed, 3 insertions(+), 39 deletions(-) diff --git a/src/Infrastructure/BotSharp.Abstraction/Browsing/Models/PageActionArgs.cs b/src/Infrastructure/BotSharp.Abstraction/Browsing/Models/PageActionArgs.cs index 16232e6b..fe575462 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Browsing/Models/PageActionArgs.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Browsing/Models/PageActionArgs.cs @@ -38,8 +38,6 @@ public class PageActionArgs public bool ResponseInMemory { get; set; } = false; public List? ResponseContainer { get; set; } - public bool UseExistingPage { get; set; } = false; - public bool WaitForNetworkIdle { get; set; } = true; public float? Timeout { get; set; } diff --git a/src/Plugins/BotSharp.Plugin.WebDriver/Drivers/PlaywrightDriver/PlaywrightInstance.cs b/src/Plugins/BotSharp.Plugin.WebDriver/Drivers/PlaywrightDriver/PlaywrightInstance.cs index 1b0e79ac..b5155cd5 100644 --- a/src/Plugins/BotSharp.Plugin.WebDriver/Drivers/PlaywrightDriver/PlaywrightInstance.cs +++ b/src/Plugins/BotSharp.Plugin.WebDriver/Drivers/PlaywrightDriver/PlaywrightInstance.cs @@ -9,7 +9,6 @@ public class PlaywrightInstance : IDisposable public IServiceProvider Services => _services; Dictionary _contexts = new Dictionary(); Dictionary> _pages = new Dictionary>(); - Dictionary _activePage = new Dictionary(); /// /// ContextId and BrowserContext @@ -26,28 +25,8 @@ public class PlaywrightInstance : IDisposable _services = services; } - public IPage? GetPage(string contextId, string? pattern = null) + public IPage? GetPage(string contextId) { - if (string.IsNullOrEmpty(pattern)) - { - return _activePage.ContainsKey(contextId) ? _activePage[contextId] : _contexts[contextId].Pages.LastOrDefault(); - } - - foreach (var page in _contexts[contextId].Pages) - { - if (page.Url.ToLower() == pattern.ToLower()) - { - _activePage[contextId] = page; - page.BringToFrontAsync().Wait(); - return page; - } - } - - if (!string.IsNullOrEmpty(pattern)) - { - return null; - } - return _contexts[contextId].Pages.LastOrDefault(); } @@ -92,7 +71,6 @@ public class PlaywrightInstance : IDisposable _contexts[ctxId].Page += async (sender, page) => { - _activePage[ctxId] = page; _pages[ctxId].Add(page); page.Close += async (sender, e) => { @@ -240,7 +218,6 @@ public class PlaywrightInstance : IDisposable if (page != null) { await page.CloseAsync(); - _activePage[ctxId] = _pages[ctxId].LastOrDefault(); } } } diff --git a/src/Plugins/BotSharp.Plugin.WebDriver/Drivers/PlaywrightDriver/PlaywrightWebDriver.GoToPage.cs b/src/Plugins/BotSharp.Plugin.WebDriver/Drivers/PlaywrightDriver/PlaywrightWebDriver.GoToPage.cs index 9b00b010..30bd6748 100644 --- a/src/Plugins/BotSharp.Plugin.WebDriver/Drivers/PlaywrightDriver/PlaywrightWebDriver.GoToPage.cs +++ b/src/Plugins/BotSharp.Plugin.WebDriver/Drivers/PlaywrightDriver/PlaywrightWebDriver.GoToPage.cs @@ -1,5 +1,3 @@ -using Microsoft.Playwright; - namespace BotSharp.Plugin.WebDriver.Drivers.PlaywrightDriver; public partial class PlaywrightWebDriver @@ -10,24 +8,15 @@ public partial class PlaywrightWebDriver var context = await _instance.GetContext(message.ContextId); try { - var page = args.UseExistingPage ? - _instance.GetPage(message.ContextId, pattern: args.Url) : - await _instance.NewPage(message, enableResponseCallback: args.EnableResponseCallback, + var page = await _instance.NewPage(message, enableResponseCallback: args.EnableResponseCallback, responseInMemory: args.ResponseInMemory, responseContainer: args.ResponseContainer, excludeResponseUrls: args.ExcludeResponseUrls, includeResponseUrls: args.IncludeResponseUrls); - if (args.UseExistingPage && page != null && page.Url == args.Url) - { - Serilog.Log.Information($"goto existing page: {args.Url}"); - result.IsSuccess = true; - return result; - } - Serilog.Log.Information($"goto page: {args.Url}"); - if (args.UseExistingPage && args.OpenNewTab && page != null && page.Url == "about:blank") + if (args.OpenNewTab && page != null && page.Url == "about:blank") { page = await _instance.NewPage(message, enableResponseCallback: args.EnableResponseCallback, From 6ecd51b49cf4f45ac02f5ed87ae388f29b22f913 Mon Sep 17 00:00:00 2001 From: "jason.wang" Date: Fri, 8 Nov 2024 14:51:56 +0800 Subject: [PATCH 04/29] fix affiliate claim name --- .../BotSharp.Core/Users/Services/UserService.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Infrastructure/BotSharp.Core/Users/Services/UserService.cs b/src/Infrastructure/BotSharp.Core/Users/Services/UserService.cs index daf8a340..441908f3 100644 --- a/src/Infrastructure/BotSharp.Core/Users/Services/UserService.cs +++ b/src/Infrastructure/BotSharp.Core/Users/Services/UserService.cs @@ -317,8 +317,8 @@ public class UserService : IUserService new Claim("role", user.Role ?? UserRole.User), new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()), new Claim("phone", user.Phone ?? string.Empty), - new Claim("affiliateId", user.AffiliateId ?? string.Empty), - new Claim("employeeId", user.EmployeeId ?? string.Empty), + new Claim("affiliate_id", user.AffiliateId ?? string.Empty), + new Claim("employee_id", user.EmployeeId ?? string.Empty), new Claim("regionCode", user.RegionCode ?? "CN") }; From d3053d3fec93fe80844d728573c9aa7968cabcb5 Mon Sep 17 00:00:00 2001 From: AnonymousDotNet <18776095145@163.com> Date: Fri, 8 Nov 2024 19:17:16 +0800 Subject: [PATCH 05/29] feat: Add regionCode to mobile number judgment --- .../Repositories/IBotSharpRepository.cs | 2 +- .../BotSharp.Abstraction/Users/IUserService.cs | 2 +- .../BotSharp.Core/Users/Services/UserService.cs | 4 ++-- .../BotSharp.OpenAPI/Controllers/UserController.cs | 6 +++--- .../Repository/MongoRepository.User.cs | 4 ++-- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/Infrastructure/BotSharp.Abstraction/Repositories/IBotSharpRepository.cs b/src/Infrastructure/BotSharp.Abstraction/Repositories/IBotSharpRepository.cs index 57900aa0..21b6bb8d 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Repositories/IBotSharpRepository.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Repositories/IBotSharpRepository.cs @@ -18,7 +18,7 @@ public interface IBotSharpRepository : IHaveServiceProvider #region User User? GetUserByEmail(string email) => throw new NotImplementedException(); - User? GetUserByPhone(string phone) => throw new NotImplementedException(); + User? GetUserByPhone(string phone, string regionCode = "CN") => throw new NotImplementedException(); User? GetAffiliateUserByPhone(string phone) => throw new NotImplementedException(); User? GetUserById(string id) => throw new NotImplementedException(); List GetUserByIds(List ids) => throw new NotImplementedException(); diff --git a/src/Infrastructure/BotSharp.Abstraction/Users/IUserService.cs b/src/Infrastructure/BotSharp.Abstraction/Users/IUserService.cs index 2a869d02..a5803600 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Users/IUserService.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Users/IUserService.cs @@ -16,7 +16,7 @@ public interface IUserService Task GetMyProfile(); Task VerifyUserNameExisting(string userName); Task VerifyEmailExisting(string email); - Task VerifyPhoneExisting(string phone); + Task VerifyPhoneExisting(string phone, string regionCode); Task SendVerificationCodeResetPasswordNoLogin(User user); Task SendVerificationCodeResetPasswordLogin(); Task ResetUserPassword(User user); diff --git a/src/Infrastructure/BotSharp.Core/Users/Services/UserService.cs b/src/Infrastructure/BotSharp.Core/Users/Services/UserService.cs index daf8a340..d5d0753d 100644 --- a/src/Infrastructure/BotSharp.Core/Users/Services/UserService.cs +++ b/src/Infrastructure/BotSharp.Core/Users/Services/UserService.cs @@ -487,7 +487,7 @@ public class UserService : IUserService return false; } - public async Task VerifyPhoneExisting(string phone) + public async Task VerifyPhoneExisting(string phone, string regionCode) { if (string.IsNullOrEmpty(phone)) { @@ -495,7 +495,7 @@ public class UserService : IUserService } var db = _services.GetRequiredService(); - var UserByphone = db.GetUserByPhone(phone); + var UserByphone = db.GetUserByPhone(phone, regionCode); if (UserByphone != null && UserByphone.Verified) { return true; diff --git a/src/Infrastructure/BotSharp.OpenAPI/Controllers/UserController.cs b/src/Infrastructure/BotSharp.OpenAPI/Controllers/UserController.cs index 90bf20f1..0b647e0d 100644 --- a/src/Infrastructure/BotSharp.OpenAPI/Controllers/UserController.cs +++ b/src/Infrastructure/BotSharp.OpenAPI/Controllers/UserController.cs @@ -1,5 +1,5 @@ -using BotSharp.Abstraction.Users.Settings; using BotSharp.Abstraction.Users.Enums; +using BotSharp.Abstraction.Users.Settings; using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authentication.Cookies; using System.ComponentModel.DataAnnotations; @@ -124,9 +124,9 @@ public class UserController : ControllerBase [AllowAnonymous] [HttpGet("/user/phone/existing")] - public async Task VerifyPhoneExisting([FromQuery] string phone) + public async Task VerifyPhoneExisting([FromQuery] string phone, [FromQuery] string regionCode = "CN") { - return await _userService.VerifyPhoneExisting(phone); + return await _userService.VerifyPhoneExisting(phone, regionCode); } [AllowAnonymous] diff --git a/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.User.cs b/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.User.cs index 19d933a9..e20c95c6 100644 --- a/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.User.cs +++ b/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.User.cs @@ -13,7 +13,7 @@ public partial class MongoRepository return user != null ? user.ToUser() : null; } - public User? GetUserByPhone(string phone) + public User? GetUserByPhone(string phone, string regionCode = "CN") { string phoneSecond = string.Empty; // 如果电话号码长度小于 4,直接返回 null @@ -29,7 +29,7 @@ public partial class MongoRepository { phoneSecond = phone.Replace("+86", ""); } - var user = _dc.Users.AsQueryable().FirstOrDefault(x => (x.Phone == phone || x.Phone == phoneSecond) && x.Type != UserType.Affiliate); + var user = _dc.Users.AsQueryable().FirstOrDefault(x => (x.Phone == phone || x.Phone == phoneSecond) && x.Type != UserType.Affiliate && x.RegionCode == regionCode); return user != null ? user.ToUser() : null; } From cb6f788295d41a7c5a6ec57aa5685a446fac71bd Mon Sep 17 00:00:00 2001 From: Haiping Chen Date: Fri, 8 Nov 2024 18:10:15 +0000 Subject: [PATCH 06/29] Add eventbus in Redis --- .../Infrastructures/Events/IEventPublisher.cs | 14 ++++ .../Events/IEventSubscriber.cs | 8 ++ .../BotSharp.Core/BotSharpCoreExtensions.cs | 19 ++++- .../Infrastructures/DistributedLocker.cs | 31 ++------ .../Infrastructures/Events/RedisPublisher.cs | 32 ++++++++ .../Infrastructures/Events/RedisSubscriber.cs | 73 +++++++++++++++++++ src/Infrastructure/BotSharp.Core/Using.cs | 3 +- .../BotSharpOpenApiExtensions.cs | 2 +- 8 files changed, 152 insertions(+), 30 deletions(-) create mode 100644 src/Infrastructure/BotSharp.Abstraction/Infrastructures/Events/IEventPublisher.cs create mode 100644 src/Infrastructure/BotSharp.Abstraction/Infrastructures/Events/IEventSubscriber.cs create mode 100644 src/Infrastructure/BotSharp.Core/Infrastructures/Events/RedisPublisher.cs create mode 100644 src/Infrastructure/BotSharp.Core/Infrastructures/Events/RedisSubscriber.cs diff --git a/src/Infrastructure/BotSharp.Abstraction/Infrastructures/Events/IEventPublisher.cs b/src/Infrastructure/BotSharp.Abstraction/Infrastructures/Events/IEventPublisher.cs new file mode 100644 index 00000000..471d810e --- /dev/null +++ b/src/Infrastructure/BotSharp.Abstraction/Infrastructures/Events/IEventPublisher.cs @@ -0,0 +1,14 @@ +namespace BotSharp.Abstraction.Infrastructures.Events; + +public interface IEventPublisher +{ + /// + /// Boardcast message to all subscribers + /// + /// + /// + /// + Task BroadcastAsync(string channel, string message); + + Task PublishAsync(string channel, string message); +} diff --git a/src/Infrastructure/BotSharp.Abstraction/Infrastructures/Events/IEventSubscriber.cs b/src/Infrastructure/BotSharp.Abstraction/Infrastructures/Events/IEventSubscriber.cs new file mode 100644 index 00000000..f295e54e --- /dev/null +++ b/src/Infrastructure/BotSharp.Abstraction/Infrastructures/Events/IEventSubscriber.cs @@ -0,0 +1,8 @@ +namespace BotSharp.Abstraction.Infrastructures.Events; + +public interface IEventSubscriber +{ + Task SubscribeAsync(string channel, Func received); + + Task SubscribeAsync(string channel, string group, Func received); +} diff --git a/src/Infrastructure/BotSharp.Core/BotSharpCoreExtensions.cs b/src/Infrastructure/BotSharp.Core/BotSharpCoreExtensions.cs index 4bdfe0d7..812d89a8 100644 --- a/src/Infrastructure/BotSharp.Core/BotSharpCoreExtensions.cs +++ b/src/Infrastructure/BotSharp.Core/BotSharpCoreExtensions.cs @@ -9,6 +9,8 @@ using BotSharp.Abstraction.Users.Settings; using BotSharp.Abstraction.Interpreters.Settings; using BotSharp.Abstraction.Infrastructures; using BotSharp.Core.Processors; +using StackExchange.Redis; +using BotSharp.Core.Infrastructures.Events; namespace BotSharp.Core; @@ -34,10 +36,12 @@ public static class BotSharpCoreExtensions services.AddSingleton(x => cacheSettings); services.AddSingleton(); + AddRedisEvents(services, config); + services.AddMemoryCache(); RegisterPlugins(services, config); - ConfigureBotSharpOptions(services, configOptions); + AddBotSharpOptions(services, configOptions); return services; } @@ -79,7 +83,7 @@ public static class BotSharpCoreExtensions return app; } - private static void ConfigureBotSharpOptions(IServiceCollection services, Action? configure) + private static void AddBotSharpOptions(IServiceCollection services, Action? configure) { var options = new BotSharpOptions(); if (configure != null) @@ -91,6 +95,17 @@ public static class BotSharpCoreExtensions services.AddSingleton(options); } + private static void AddRedisEvents(IServiceCollection services, IConfiguration config) + { + // Add Redis connection as a singleton + var dbSettings = new BotSharpDatabaseSettings(); + config.Bind("Database", dbSettings); + + services.AddSingleton(ConnectionMultiplexer.Connect(dbSettings.Redis)); + services.AddSingleton(); + services.AddSingleton(); + } + private static void AddDefaultJsonConverters(BotSharpOptions options) { options.JsonSerializerOptions.Converters.Add(new RichContentJsonConverter()); diff --git a/src/Infrastructure/BotSharp.Core/Infrastructures/DistributedLocker.cs b/src/Infrastructure/BotSharp.Core/Infrastructures/DistributedLocker.cs index 25c3a047..e37145ff 100644 --- a/src/Infrastructure/BotSharp.Core/Infrastructures/DistributedLocker.cs +++ b/src/Infrastructure/BotSharp.Core/Infrastructures/DistributedLocker.cs @@ -5,21 +5,18 @@ namespace BotSharp.Core.Infrastructures; public class DistributedLocker { - private readonly BotSharpDatabaseSettings _settings; - private static ConnectionMultiplexer connection; + private readonly IConnectionMultiplexer _redis; - public DistributedLocker(BotSharpDatabaseSettings settings) + public DistributedLocker(IConnectionMultiplexer redis) { - _settings = settings; + _redis = redis; } public async Task Lock(string resource, Func> action, int timeoutInSeconds = 30) { - await ConnectToRedisAsync(); - var timeout = TimeSpan.FromSeconds(timeoutInSeconds); - var @lock = new RedisDistributedLock(resource, connection.GetDatabase()); + var @lock = new RedisDistributedLock(resource, _redis.GetDatabase()); await using (var handle = await @lock.TryAcquireAsync(timeout)) { if (handle == null) @@ -33,11 +30,9 @@ public class DistributedLocker public void Lock(string resource, Action action, int timeoutInSeconds = 30) { - ConnectToRedis(); - var timeout = TimeSpan.FromSeconds(timeoutInSeconds); - var @lock = new RedisDistributedLock(resource, connection.GetDatabase()); + var @lock = new RedisDistributedLock(resource, _redis.GetDatabase()); using (var handle = @lock.TryAcquire(timeout)) { if (handle == null) @@ -50,20 +45,4 @@ public class DistributedLocker } } } - - private void ConnectToRedis() - { - if (connection == null) - { - connection = ConnectionMultiplexer.Connect(_settings.Redis); - } - } - - private async Task ConnectToRedisAsync() - { - if (connection == null) - { - connection = await ConnectionMultiplexer.ConnectAsync(_settings.Redis); - } - } } diff --git a/src/Infrastructure/BotSharp.Core/Infrastructures/Events/RedisPublisher.cs b/src/Infrastructure/BotSharp.Core/Infrastructures/Events/RedisPublisher.cs new file mode 100644 index 00000000..4f3ad405 --- /dev/null +++ b/src/Infrastructure/BotSharp.Core/Infrastructures/Events/RedisPublisher.cs @@ -0,0 +1,32 @@ +using StackExchange.Redis; + +namespace BotSharp.Core.Infrastructures.Events; + +public class RedisPublisher : IEventPublisher +{ + private readonly IConnectionMultiplexer _redis; + private readonly ISubscriber _subscriber; + private readonly ILogger _logger; + + public RedisPublisher(IConnectionMultiplexer redis, ILogger logger) + { + _redis = redis; + _logger = logger; + _subscriber = _redis.GetSubscriber(); + } + + public async Task BroadcastAsync(string channel, string message) + { + await _subscriber.PublishAsync(channel, message); + } + + public async Task PublishAsync(string channel, string message) + { + var db = _redis.GetDatabase(); + // Add a message to the stream, keeping only the latest 1 million messages + await db.StreamAddAsync(channel, "message", message, + maxLength: 1000 * 10000); + + _logger.LogInformation($"Published message {channel} {message}"); + } +} diff --git a/src/Infrastructure/BotSharp.Core/Infrastructures/Events/RedisSubscriber.cs b/src/Infrastructure/BotSharp.Core/Infrastructures/Events/RedisSubscriber.cs new file mode 100644 index 00000000..39bb1f91 --- /dev/null +++ b/src/Infrastructure/BotSharp.Core/Infrastructures/Events/RedisSubscriber.cs @@ -0,0 +1,73 @@ +using StackExchange.Redis; +using System.Threading.Channels; + +namespace BotSharp.Core.Infrastructures.Events; + +public class RedisSubscriber : IEventSubscriber +{ + private readonly IConnectionMultiplexer _redis; + private readonly ISubscriber _subscriber; + private readonly ILogger _logger; + + public RedisSubscriber(IConnectionMultiplexer redis, ILogger logger) + { + _redis = redis; + _logger = logger; + _subscriber = _redis.GetSubscriber(); + } + + public async Task SubscribeAsync(string channel, Func received) + { + await _subscriber.SubscribeAsync(channel, async (ch, message) => + { + _logger.LogInformation($"Received event from channel: {ch} message: {message}"); + await received(ch, message); + }); + } + + public async Task SubscribeAsync(string channel, string group, Func received) + { + var db = _redis.GetDatabase(); + + // Create the consumer group if it doesn't exist + try + { + await db.StreamCreateConsumerGroupAsync(channel, group, StreamPosition.NewMessages, createStream: true); + } + catch (RedisServerException ex) when (ex.Message.Contains("BUSYGROUP")) + { + // Group already exists, ignore the error + _logger.LogWarning($"Consumer group '{group}' already exists (caught exception)."); + } + catch (Exception ex) + { + _logger.LogError($"Error creating consumer group: '{group}' {ex.Message}"); + throw; + } + + while (true) + { + var entries = await db.StreamReadGroupAsync(channel, group, Environment.MachineName, count: 1); + foreach (var entry in entries) + { + _logger.LogInformation($"Consumer {Environment.MachineName} received: {channel} {entry.Values[0].Value}"); + await db.StreamAcknowledgeAsync(channel, group, entry.Id); + + try + { + await received(channel, entry.Values[0].Value); + + // Optionally delete the message to save space + await db.StreamDeleteAsync(channel, [entry.Id]); + } + catch (Exception ex) + { + _logger.LogError($"Error processing message: {ex.Message}, event id: {channel} {entry.Id}"); + } + } + + await Task.Delay(Random.Shared.Next(1, 11) * 100); + } + + } +} diff --git a/src/Infrastructure/BotSharp.Core/Using.cs b/src/Infrastructure/BotSharp.Core/Using.cs index 8a0ca2af..9e9177ba 100644 --- a/src/Infrastructure/BotSharp.Core/Using.cs +++ b/src/Infrastructure/BotSharp.Core/Using.cs @@ -40,4 +40,5 @@ global using BotSharp.Core.Agents.Services; global using BotSharp.Core.Conversations.Services; global using BotSharp.Core.Infrastructures; global using BotSharp.Core.Users.Services; -global using Aspects.Cache; \ No newline at end of file +global using Aspects.Cache; +global using BotSharp.Abstraction.Infrastructures.Events; \ No newline at end of file diff --git a/src/Infrastructure/BotSharp.OpenAPI/BotSharpOpenApiExtensions.cs b/src/Infrastructure/BotSharp.OpenAPI/BotSharpOpenApiExtensions.cs index efad6e6c..4d85167f 100644 --- a/src/Infrastructure/BotSharp.OpenAPI/BotSharpOpenApiExtensions.cs +++ b/src/Infrastructure/BotSharp.OpenAPI/BotSharpOpenApiExtensions.cs @@ -209,7 +209,7 @@ public static class BotSharpOpenApiExtensions app.UseSwagger(); - // if (env.IsDevelopment()) + if (env.IsDevelopment()) { IdentityModelEventSource.ShowPII = true; app.UseSwaggerUI(); From 4f9cd09b404523641954feb75911a3b4c464b3df Mon Sep 17 00:00:00 2001 From: Haiping Chen Date: Fri, 8 Nov 2024 20:19:52 +0000 Subject: [PATCH 07/29] remove duplicate DistributedLocker --- src/Infrastructure/BotSharp.Core/BotSharpCoreExtensions.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/Infrastructure/BotSharp.Core/BotSharpCoreExtensions.cs b/src/Infrastructure/BotSharp.Core/BotSharpCoreExtensions.cs index 812d89a8..862ef899 100644 --- a/src/Infrastructure/BotSharp.Core/BotSharpCoreExtensions.cs +++ b/src/Infrastructure/BotSharp.Core/BotSharpCoreExtensions.cs @@ -28,8 +28,6 @@ public static class BotSharpCoreExtensions services.AddScoped(); services.AddScoped(); - services.AddSingleton(); - // Register cache service var cacheSettings = new SharpCacheSettings(); config.Bind("SharpCache", cacheSettings); From 0d89528562ca2b69ecd3649a5942700dea07a25c Mon Sep 17 00:00:00 2001 From: Haiping Chen Date: Fri, 8 Nov 2024 20:53:04 +0000 Subject: [PATCH 08/29] _logger in DistributedLocker --- .../Infrastructures/DistributedLocker.cs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/Infrastructure/BotSharp.Core/Infrastructures/DistributedLocker.cs b/src/Infrastructure/BotSharp.Core/Infrastructures/DistributedLocker.cs index e37145ff..32c7af52 100644 --- a/src/Infrastructure/BotSharp.Core/Infrastructures/DistributedLocker.cs +++ b/src/Infrastructure/BotSharp.Core/Infrastructures/DistributedLocker.cs @@ -6,10 +6,12 @@ namespace BotSharp.Core.Infrastructures; public class DistributedLocker { private readonly IConnectionMultiplexer _redis; + private readonly ILogger _logger; - public DistributedLocker(IConnectionMultiplexer redis) + public DistributedLocker(IConnectionMultiplexer redis, ILogger logger) { _redis = redis; + _logger = logger; } public async Task Lock(string resource, Func> action, int timeoutInSeconds = 30) @@ -21,14 +23,14 @@ public class DistributedLocker { if (handle == null) { - Serilog.Log.Logger.Error($"Acquire lock for {resource} failed due to after {timeout}s timeout."); + _logger.LogWarning($"Acquire lock for {resource} failed due to after {timeout}s timeout."); } return await action(); } } - public void Lock(string resource, Action action, int timeoutInSeconds = 30) + public bool Lock(string resource, Action action, int timeoutInSeconds = 30) { var timeout = TimeSpan.FromSeconds(timeoutInSeconds); @@ -37,11 +39,13 @@ public class DistributedLocker { if (handle == null) { - Serilog.Log.Logger.Error($"Acquire lock for {resource} failed due to after {timeout}s timeout."); + _logger.LogWarning($"Acquire lock for {resource} failed due to after {timeout}s timeout."); + return false; } else { action(); + return true; } } } From e929a46bee4d39c99b6eec3d72a346ff3a1aa21f Mon Sep 17 00:00:00 2001 From: danijerez Date: Sat, 9 Nov 2024 15:07:42 +0100 Subject: [PATCH 09/29] add plugin provider vertexai --- BotSharp.sln | 13 +++- .../BotSharp.Plugin.VertexAI.csproj | 21 ++++++ .../Providers/ChatCompletionProvider.cs | 72 +++++++++++++++++++ .../Providers/TextCompletionProvider.cs | 65 +++++++++++++++++ .../VertexAiPlugin.cs | 30 ++++++++ 5 files changed, 200 insertions(+), 1 deletion(-) create mode 100644 src/Plugins/BotSharp.Plugin.LangChain/BotSharp.Plugin.VertexAI.csproj create mode 100644 src/Plugins/BotSharp.Plugin.LangChain/Providers/ChatCompletionProvider.cs create mode 100644 src/Plugins/BotSharp.Plugin.LangChain/Providers/TextCompletionProvider.cs create mode 100644 src/Plugins/BotSharp.Plugin.LangChain/VertexAiPlugin.cs diff --git a/BotSharp.sln b/BotSharp.sln index 93f289ba..52b3f9d0 100644 --- a/BotSharp.sln +++ b/BotSharp.sln @@ -117,7 +117,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BotSharp.Plugin.Graph", "sr EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BotSharp.Plugin.AudioHandler", "src\Plugins\BotSharp.Plugin.AudioHandler\BotSharp.Plugin.AudioHandler.csproj", "{F57F4862-F8D4-44A1-AC12-5C131B5C9785}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BotSharp.Core.SideCar", "src\Infrastructure\BotSharp.Core.SideCar\BotSharp.Core.SideCar.csproj", "{6D3A54F9-4792-41DB-BE7D-4F7B1D918EAE}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BotSharp.Core.SideCar", "src\Infrastructure\BotSharp.Core.SideCar\BotSharp.Core.SideCar.csproj", "{6D3A54F9-4792-41DB-BE7D-4F7B1D918EAE}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BotSharp.Plugin.VertexAI", "src\Plugins\BotSharp.Plugin.LangChain\BotSharp.Plugin.VertexAI.csproj", "{7DA2DCD0-551B-432E-AA5C-22DDD3ED459B}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -479,6 +481,14 @@ Global {6D3A54F9-4792-41DB-BE7D-4F7B1D918EAE}.Release|Any CPU.Build.0 = Release|Any CPU {6D3A54F9-4792-41DB-BE7D-4F7B1D918EAE}.Release|x64.ActiveCfg = Release|Any CPU {6D3A54F9-4792-41DB-BE7D-4F7B1D918EAE}.Release|x64.Build.0 = Release|Any CPU + {7DA2DCD0-551B-432E-AA5C-22DDD3ED459B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7DA2DCD0-551B-432E-AA5C-22DDD3ED459B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7DA2DCD0-551B-432E-AA5C-22DDD3ED459B}.Debug|x64.ActiveCfg = Debug|Any CPU + {7DA2DCD0-551B-432E-AA5C-22DDD3ED459B}.Debug|x64.Build.0 = Debug|Any CPU + {7DA2DCD0-551B-432E-AA5C-22DDD3ED459B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7DA2DCD0-551B-432E-AA5C-22DDD3ED459B}.Release|Any CPU.Build.0 = Release|Any CPU + {7DA2DCD0-551B-432E-AA5C-22DDD3ED459B}.Release|x64.ActiveCfg = Release|Any CPU + {7DA2DCD0-551B-432E-AA5C-22DDD3ED459B}.Release|x64.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -536,6 +546,7 @@ Global {EBFE97DA-D0BA-48BA-8B5D-083B60348D1D} = {97A0B191-64D7-4F8A-BFE8-1BFCC5E247E1} {F57F4862-F8D4-44A1-AC12-5C131B5C9785} = {51AFE054-AE99-497D-A593-69BAEFB5106F} {6D3A54F9-4792-41DB-BE7D-4F7B1D918EAE} = {E29DC6C4-5E57-48C5-BCB0-6B8F84782749} + {7DA2DCD0-551B-432E-AA5C-22DDD3ED459B} = {D5293208-2BEF-42FC-A64C-5954F61720BA} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {A9969D89-C98B-40A5-A12B-FC87E55B3A19} diff --git a/src/Plugins/BotSharp.Plugin.LangChain/BotSharp.Plugin.VertexAI.csproj b/src/Plugins/BotSharp.Plugin.LangChain/BotSharp.Plugin.VertexAI.csproj new file mode 100644 index 00000000..004d7233 --- /dev/null +++ b/src/Plugins/BotSharp.Plugin.LangChain/BotSharp.Plugin.VertexAI.csproj @@ -0,0 +1,21 @@ + + + + $(TargetFramework) + enable + $(LangVersion) + $(BotSharpVersion) + $(GeneratePackageOnBuild) + $(GenerateDocumentationFile) + $(SolutionDir)packages + + + + + + + + + + + diff --git a/src/Plugins/BotSharp.Plugin.LangChain/Providers/ChatCompletionProvider.cs b/src/Plugins/BotSharp.Plugin.LangChain/Providers/ChatCompletionProvider.cs new file mode 100644 index 00000000..d9531039 --- /dev/null +++ b/src/Plugins/BotSharp.Plugin.LangChain/Providers/ChatCompletionProvider.cs @@ -0,0 +1,72 @@ +using BotSharp.Abstraction.Agents.Enums; +using BotSharp.Abstraction.Agents.Models; +using BotSharp.Abstraction.Conversations.Models; +using BotSharp.Abstraction.Loggers; +using BotSharp.Abstraction.MLTasks; +using LangChain.Providers; +using LangChain.Providers.Google.VertexAI; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace BotSharp.Plugin.VertexAI.Providers +{ + public class ChatCompletionProvider( + VertexAIConfiguration config, + ChatSettings settings, + ILogger logger, + IServiceProvider services) : IChatCompletion + { + public string Provider => "vertexai"; + private readonly VertexAIConfiguration _config = config; + private readonly ChatSettings? _settings = settings; + private readonly IServiceProvider _services = services; + private readonly ILogger _logger = logger; + public required string _model; + + public void SetModelName(string model) + { + _model = model; + } + + public async Task GetChatCompletions(Agent agent, List conversations) + { + var hooks = _services.GetServices().ToList(); + Task.WaitAll(hooks.Select(hook => + hook.BeforeGenerating(agent, conversations)).ToArray()); + var client = new VertexAIProvider(_config); + var model = new VertexAIChatModel(client, _model); + var messages = conversations + .Select(c => new Message(c.Content, c.Role == AgentRole.User ? MessageRole.Human : MessageRole.Ai)).ToList(); + + var response = await model.GenerateAsync(new ChatRequest { Messages = messages }, _settings); + + var msg = new RoleDialogModel(MessageRole.Ai.ToString(), response.LastMessageContent) + { + CurrentAgentId = agent.Id + }; + + Task.WaitAll(hooks.Select(hook => + hook.AfterGenerated(msg, new TokenStatsModel + { + Prompt = response.Messages[0].Content, + Model = _model + })).ToArray()); + + return msg; + } + + public Task GetChatCompletionsAsync(Agent agent, List conversations, Func onMessageReceived, Func onFunctionExecuting) + { + throw new NotImplementedException(); + } + + public Task GetChatCompletionsStreamingAsync(Agent agent, List conversations, Func onMessageReceived) + { + throw new NotImplementedException(); + } + } +} diff --git a/src/Plugins/BotSharp.Plugin.LangChain/Providers/TextCompletionProvider.cs b/src/Plugins/BotSharp.Plugin.LangChain/Providers/TextCompletionProvider.cs new file mode 100644 index 00000000..94820207 --- /dev/null +++ b/src/Plugins/BotSharp.Plugin.LangChain/Providers/TextCompletionProvider.cs @@ -0,0 +1,65 @@ +using BotSharp.Abstraction.Agents.Enums; +using BotSharp.Abstraction.Agents.Models; +using BotSharp.Abstraction.Conversations.Models; +using BotSharp.Abstraction.Loggers; +using BotSharp.Abstraction.MLTasks; +using LangChain.Providers; +using LangChain.Providers.Google.VertexAI; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using System; +using System.Linq; +using System.Threading.Tasks; + +namespace BotSharp.Plugin.VertexAI.Providers +{ + public class TextCompletionProvider( + VertexAIConfiguration config, + ChatSettings settings, + ILogger logger, + IServiceProvider services) : ITextCompletion + { + public string Provider => "vertexai"; + private readonly VertexAIConfiguration _config = config; + private readonly ChatSettings? _settings = settings; + private readonly IServiceProvider _services = services; + private readonly ILogger _logger = logger; + public required string _model; + + public async Task GetCompletion(string text, string agentId, string messageId) + { + var contentHooks = _services.GetServices().ToList(); + var agent = new Agent() + { + Id = agentId, + }; + + var client = new VertexAIProvider(_config); + var model = new VertexAIChatModel(client, _model); + var response = await model.GenerateAsync(text, _settings); + + var responseMessage = new RoleDialogModel(AgentRole.Assistant, response.LastMessageContent) + { + CurrentAgentId = agentId, + MessageId = messageId + }; + + Task.WaitAll(contentHooks.Select(hook => + hook.AfterGenerated(responseMessage, new TokenStatsModel + { + Prompt = text, + Provider = Provider, + Model = _model, + PromptCount = response.Usage.TotalTokens, + CompletionCount = response.Usage.OutputTokens + })).ToArray()); + + return response.LastMessageContent; + } + + public void SetModelName(string model) + { + _model = model; + } + } +} diff --git a/src/Plugins/BotSharp.Plugin.LangChain/VertexAiPlugin.cs b/src/Plugins/BotSharp.Plugin.LangChain/VertexAiPlugin.cs new file mode 100644 index 00000000..e9d7960b --- /dev/null +++ b/src/Plugins/BotSharp.Plugin.LangChain/VertexAiPlugin.cs @@ -0,0 +1,30 @@ +using BotSharp.Abstraction.MLTasks; +using BotSharp.Abstraction.Plugins; +using BotSharp.Abstraction.Settings; +using BotSharp.Plugin.VertexAI.Providers; +using LangChain.Providers.Google.VertexAI; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using System; + +namespace BotSharp.Plugin.VertexAI +{ + public class VertexAiPlugin : IBotSharpPlugin + { + public string Id => "962ff441-2b40-4db4-b530-49efb1688a75"; + public string Name => "VertexAI"; + public string Description => "VertexAI Service including text generation, text to image and other AI services."; + public string IconUrl => "https://upload.wikimedia.org/wikipedia/commons/thumb/0/05/Vertex_AI_Logo.svg/480px-Vertex_AI_Logo.svg.png"; + + public void RegisterDI(IServiceCollection services, IConfiguration config) + { + services.AddScoped(provider => + { + var settingService = provider.GetRequiredService(); + return settingService.Bind("VertexAI"); + }); + services.AddScoped(); + services.AddScoped(); + } + } +} From b4e25f309670313b5de32fd6478bb1bb0926f6e9 Mon Sep 17 00:00:00 2001 From: Haiping Chen Date: Mon, 11 Nov 2024 04:13:50 +0000 Subject: [PATCH 10/29] ReDispatchAsync --- .../Infrastructures/Events/IEventPublisher.cs | 2 ++ .../Infrastructures/Events/RedisPublisher.cs | 25 +++++++++++++++++++ 2 files changed, 27 insertions(+) diff --git a/src/Infrastructure/BotSharp.Abstraction/Infrastructures/Events/IEventPublisher.cs b/src/Infrastructure/BotSharp.Abstraction/Infrastructures/Events/IEventPublisher.cs index 471d810e..e242937e 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Infrastructures/Events/IEventPublisher.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Infrastructures/Events/IEventPublisher.cs @@ -11,4 +11,6 @@ public interface IEventPublisher Task BroadcastAsync(string channel, string message); Task PublishAsync(string channel, string message); + + Task ReDispatchAsync(string channel, int count = 10, string order = "asc"); } diff --git a/src/Infrastructure/BotSharp.Core/Infrastructures/Events/RedisPublisher.cs b/src/Infrastructure/BotSharp.Core/Infrastructures/Events/RedisPublisher.cs index 4f3ad405..f6daa59d 100644 --- a/src/Infrastructure/BotSharp.Core/Infrastructures/Events/RedisPublisher.cs +++ b/src/Infrastructure/BotSharp.Core/Infrastructures/Events/RedisPublisher.cs @@ -29,4 +29,29 @@ public class RedisPublisher : IEventPublisher _logger.LogInformation($"Published message {channel} {message}"); } + + public async Task ReDispatchAsync(string channel, int count = 10, string order = "asc") + { + var db = _redis.GetDatabase(); + + var entries = await db.StreamRangeAsync(channel, "-", "+", count: count, messageOrder: order == "asc" ? Order.Ascending : Order.Descending); + foreach (var entry in entries) + { + _logger.LogInformation($"Fetched message: {channel} {entry.Values[0].Value} ({entry.Id})"); + + try + { + var messageId = await db.StreamAddAsync(channel, "message", entry.Values[0].Value); + + _logger.LogWarning($"ReDispatched message: {channel} {entry.Values[0].Value} ({messageId})"); + + // Optionally delete the message to save space + await db.StreamDeleteAsync(channel, [entry.Id]); + } + catch (Exception ex) + { + _logger.LogError($"Error processing message: {ex.Message}, event id: {channel} {entry.Id}"); + } + } + } } From e2e8f23df1d569b8447d96f42155abcb585c5a75 Mon Sep 17 00:00:00 2001 From: AnonymousDotNet <18776095145@163.com> Date: Mon, 11 Nov 2024 13:40:21 +0800 Subject: [PATCH 11/29] fix: update GetUserByPhone API --- .../Repository/MongoRepository.User.cs | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.User.cs b/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.User.cs index e20c95c6..fb69c4a2 100644 --- a/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.User.cs +++ b/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.User.cs @@ -15,21 +15,15 @@ public partial class MongoRepository public User? GetUserByPhone(string phone, string regionCode = "CN") { - string phoneSecond = string.Empty; - // 如果电话号码长度小于 4,直接返回 null - if (phone?.Length < 4) + if (phone == null || phone.Length < 4) { return null; } - if (phone.Substring(0, 3) != "+86") - { - phoneSecond = $"+86{phone}"; - } - else - { - phoneSecond = phone.Replace("+86", ""); - } - var user = _dc.Users.AsQueryable().FirstOrDefault(x => (x.Phone == phone || x.Phone == phoneSecond) && x.Type != UserType.Affiliate && x.RegionCode == regionCode); + + string phoneSecond = phone.StartsWith("+86") ? phone.Replace("+86", "") : $"+86{phone}"; + + //var user = _dc.Users.AsQueryable().FirstOrDefault(x => (x.Phone == phone || x.Phone == phoneSecond) && x.Type != UserType.Affiliate && x.RegionCode == regionCode); + var user = _dc.Users.AsQueryable().FirstOrDefault(x => (x.Phone == phone || x.Phone == phoneSecond) && x.Type != UserType.Affiliate && (x.RegionCode == regionCode || x.RegionCode == null)); return user != null ? user.ToUser() : null; } From 6f323018d8b9bc4efc6ac5d4245289d5b9632018 Mon Sep 17 00:00:00 2001 From: AnonymousDotNet <18776095145@163.com> Date: Mon, 11 Nov 2024 13:49:45 +0800 Subject: [PATCH 12/29] perf: GetUserByPhone --- .../Repository/MongoRepository.User.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.User.cs b/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.User.cs index fb69c4a2..2697fffa 100644 --- a/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.User.cs +++ b/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.User.cs @@ -15,14 +15,13 @@ public partial class MongoRepository public User? GetUserByPhone(string phone, string regionCode = "CN") { - if (phone == null || phone.Length < 4) + if (string.IsNullOrWhiteSpace(phone)) { return null; } string phoneSecond = phone.StartsWith("+86") ? phone.Replace("+86", "") : $"+86{phone}"; - //var user = _dc.Users.AsQueryable().FirstOrDefault(x => (x.Phone == phone || x.Phone == phoneSecond) && x.Type != UserType.Affiliate && x.RegionCode == regionCode); var user = _dc.Users.AsQueryable().FirstOrDefault(x => (x.Phone == phone || x.Phone == phoneSecond) && x.Type != UserType.Affiliate && (x.RegionCode == regionCode || x.RegionCode == null)); return user != null ? user.ToUser() : null; } From 4142e99b31f1e942f7782fc01fc9129724b25938 Mon Sep 17 00:00:00 2001 From: AnonymousDotNet <18776095145@163.com> Date: Tue, 12 Nov 2024 10:03:20 +0800 Subject: [PATCH 13/29] fix: using IsNullOrWhiteSpace --- .../Repository/MongoRepository.User.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.User.cs b/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.User.cs index 2697fffa..4af170b0 100644 --- a/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.User.cs +++ b/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.User.cs @@ -22,7 +22,7 @@ public partial class MongoRepository string phoneSecond = phone.StartsWith("+86") ? phone.Replace("+86", "") : $"+86{phone}"; - var user = _dc.Users.AsQueryable().FirstOrDefault(x => (x.Phone == phone || x.Phone == phoneSecond) && x.Type != UserType.Affiliate && (x.RegionCode == regionCode || x.RegionCode == null)); + var user = _dc.Users.AsQueryable().FirstOrDefault(x => (x.Phone == phone || x.Phone == phoneSecond) && x.Type != UserType.Affiliate && (x.RegionCode == regionCode || string.IsNullOrWhiteSpace(x.RegionCode))); return user != null ? user.ToUser() : null; } From 5870bd434c3f175b42bdafcad4d8017413205cae Mon Sep 17 00:00:00 2001 From: danijerez Date: Wed, 13 Nov 2024 21:01:03 +0100 Subject: [PATCH 14/29] linter --- .../Providers/ChatCompletionProvider.cs | 9 +++--- .../Providers/TextCompletionProvider.cs | 9 +++--- .../VertexAiPlugin.cs | 32 +++++++++---------- 3 files changed, 23 insertions(+), 27 deletions(-) diff --git a/src/Plugins/BotSharp.Plugin.LangChain/Providers/ChatCompletionProvider.cs b/src/Plugins/BotSharp.Plugin.LangChain/Providers/ChatCompletionProvider.cs index d9531039..61f66119 100644 --- a/src/Plugins/BotSharp.Plugin.LangChain/Providers/ChatCompletionProvider.cs +++ b/src/Plugins/BotSharp.Plugin.LangChain/Providers/ChatCompletionProvider.cs @@ -14,11 +14,10 @@ using System.Threading.Tasks; namespace BotSharp.Plugin.VertexAI.Providers { - public class ChatCompletionProvider( - VertexAIConfiguration config, - ChatSettings settings, - ILogger logger, - IServiceProvider services) : IChatCompletion + public class ChatCompletionProvider(VertexAIConfiguration config, + ChatSettings settings, + ILogger logger, + IServiceProvider services) : IChatCompletion { public string Provider => "vertexai"; private readonly VertexAIConfiguration _config = config; diff --git a/src/Plugins/BotSharp.Plugin.LangChain/Providers/TextCompletionProvider.cs b/src/Plugins/BotSharp.Plugin.LangChain/Providers/TextCompletionProvider.cs index 94820207..46186452 100644 --- a/src/Plugins/BotSharp.Plugin.LangChain/Providers/TextCompletionProvider.cs +++ b/src/Plugins/BotSharp.Plugin.LangChain/Providers/TextCompletionProvider.cs @@ -13,11 +13,10 @@ using System.Threading.Tasks; namespace BotSharp.Plugin.VertexAI.Providers { - public class TextCompletionProvider( - VertexAIConfiguration config, - ChatSettings settings, - ILogger logger, - IServiceProvider services) : ITextCompletion + public class TextCompletionProvider(VertexAIConfiguration config, + ChatSettings settings, + ILogger logger, + IServiceProvider services) : ITextCompletion { public string Provider => "vertexai"; private readonly VertexAIConfiguration _config = config; diff --git a/src/Plugins/BotSharp.Plugin.LangChain/VertexAiPlugin.cs b/src/Plugins/BotSharp.Plugin.LangChain/VertexAiPlugin.cs index e9d7960b..5ea7612d 100644 --- a/src/Plugins/BotSharp.Plugin.LangChain/VertexAiPlugin.cs +++ b/src/Plugins/BotSharp.Plugin.LangChain/VertexAiPlugin.cs @@ -5,26 +5,24 @@ using BotSharp.Plugin.VertexAI.Providers; using LangChain.Providers.Google.VertexAI; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; -using System; -namespace BotSharp.Plugin.VertexAI +namespace BotSharp.Plugin.VertexAI; + +public class VertexAiPlugin : IBotSharpPlugin { - public class VertexAiPlugin : IBotSharpPlugin - { - public string Id => "962ff441-2b40-4db4-b530-49efb1688a75"; - public string Name => "VertexAI"; - public string Description => "VertexAI Service including text generation, text to image and other AI services."; - public string IconUrl => "https://upload.wikimedia.org/wikipedia/commons/thumb/0/05/Vertex_AI_Logo.svg/480px-Vertex_AI_Logo.svg.png"; + public string Id => "962ff441-2b40-4db4-b530-49efb1688a75"; + public string Name => "VertexAI"; + public string Description => "VertexAI Service including text generation, text to image and other AI services."; + public string IconUrl => "https://upload.wikimedia.org/wikipedia/commons/thumb/0/05/Vertex_AI_Logo.svg/480px-Vertex_AI_Logo.svg.png"; - public void RegisterDI(IServiceCollection services, IConfiguration config) + public void RegisterDI(IServiceCollection services, IConfiguration config) + { + services.AddScoped(provider => { - services.AddScoped(provider => - { - var settingService = provider.GetRequiredService(); - return settingService.Bind("VertexAI"); - }); - services.AddScoped(); - services.AddScoped(); - } + var settingService = provider.GetRequiredService(); + return settingService.Bind("VertexAI"); + }); + services.AddScoped(); + services.AddScoped(); } } From 9a954d8551536ccb467a40b72898a0fa2525b8eb Mon Sep 17 00:00:00 2001 From: Jicheng Lu <103353@smsassist.com> Date: Wed, 13 Nov 2024 17:16:49 -0600 Subject: [PATCH 15/29] add role --- .../Repositories/Filters/AgentFilter.cs | 5 + .../Repositories/Filters/AgentTaskFilter.cs | 5 + .../Filters/ConversationFilter.cs | 5 + .../Repositories/Filters/RoleFilter.cs | 12 +++ .../Filters}/UserFilter.cs | 7 +- .../Repositories/IBotSharpRepository.cs | 8 ++ .../Roles/IRoleService.cs | 12 +++ .../BotSharp.Abstraction/Roles/Models/Role.cs | 22 +++++ .../Roles/Models/RoleAgent.cs | 25 +++++ .../Roles/Models/RoleAgentAction.cs | 16 +++ .../Users/IUserService.cs | 4 +- .../FileRepository/FileRepository.Agent.cs | 8 +- .../FileRepository.AgentTask.cs | 5 + .../FileRepository.Conversation.cs | 5 + .../FileRepository/FileRepository.Role.cs | 97 +++++++++++++++++++ .../FileRepository/FileRepository.User.cs | 69 +++++++------ .../FileRepository/FileRepository.cs | 88 ++++++++++++++--- .../Roles/Services/RoleService.cs | 56 +++++++++++ .../Users/Services/UserService.cs | 12 ++- src/Infrastructure/BotSharp.Core/Using.cs | 2 + .../Controllers/RoleController.cs | 64 ++++++++++++ .../Controllers/UserController.cs | 7 ++ src/Infrastructure/BotSharp.OpenAPI/Using.cs | 1 + .../Roles/RoleAgentActionViewModel.cs | 42 ++++++++ .../ViewModels/Roles/RoleUpdateModel.cs | 30 ++++++ .../ViewModels/Roles/RoleViewModel.cs | 40 ++++++++ .../Repository/MongoRepository.Agent.cs | 5 + .../Repository/MongoRepository.AgentTask.cs | 5 + .../MongoRepository.Conversation.cs | 5 + .../Repository/MongoRepository.User.cs | 87 +++++++++-------- 30 files changed, 663 insertions(+), 86 deletions(-) create mode 100644 src/Infrastructure/BotSharp.Abstraction/Repositories/Filters/RoleFilter.cs rename src/Infrastructure/BotSharp.Abstraction/{Users/Models => Repositories/Filters}/UserFilter.cs (78%) create mode 100644 src/Infrastructure/BotSharp.Abstraction/Roles/IRoleService.cs create mode 100644 src/Infrastructure/BotSharp.Abstraction/Roles/Models/Role.cs create mode 100644 src/Infrastructure/BotSharp.Abstraction/Roles/Models/RoleAgent.cs create mode 100644 src/Infrastructure/BotSharp.Abstraction/Roles/Models/RoleAgentAction.cs create mode 100644 src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Role.cs create mode 100644 src/Infrastructure/BotSharp.Core/Roles/Services/RoleService.cs create mode 100644 src/Infrastructure/BotSharp.OpenAPI/Controllers/RoleController.cs create mode 100644 src/Infrastructure/BotSharp.OpenAPI/ViewModels/Roles/RoleAgentActionViewModel.cs create mode 100644 src/Infrastructure/BotSharp.OpenAPI/ViewModels/Roles/RoleUpdateModel.cs create mode 100644 src/Infrastructure/BotSharp.OpenAPI/ViewModels/Roles/RoleViewModel.cs diff --git a/src/Infrastructure/BotSharp.Abstraction/Repositories/Filters/AgentFilter.cs b/src/Infrastructure/BotSharp.Abstraction/Repositories/Filters/AgentFilter.cs index fb2bfed2..7de61b76 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Repositories/Filters/AgentFilter.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Repositories/Filters/AgentFilter.cs @@ -9,4 +9,9 @@ public class AgentFilter public string? Type { get; set; } public bool? IsPublic { get; set; } public List? AgentIds { get; set; } + + public static AgentFilter Empty() + { + return new AgentFilter(); + } } diff --git a/src/Infrastructure/BotSharp.Abstraction/Repositories/Filters/AgentTaskFilter.cs b/src/Infrastructure/BotSharp.Abstraction/Repositories/Filters/AgentTaskFilter.cs index 2d8ba477..46ab4621 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Repositories/Filters/AgentTaskFilter.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Repositories/Filters/AgentTaskFilter.cs @@ -5,4 +5,9 @@ public class AgentTaskFilter public Pagination Pager { get; set; } = new Pagination(); public string? AgentId { get; set; } public bool? Enabled { get; set; } + + public static AgentTaskFilter Empty() + { + return new AgentTaskFilter(); + } } diff --git a/src/Infrastructure/BotSharp.Abstraction/Repositories/Filters/ConversationFilter.cs b/src/Infrastructure/BotSharp.Abstraction/Repositories/Filters/ConversationFilter.cs index 6ebedc5b..ab1a0a9f 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Repositories/Filters/ConversationFilter.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Repositories/Filters/ConversationFilter.cs @@ -25,4 +25,9 @@ public class ConversationFilter public IEnumerable? States { get; set; } = []; public IEnumerable? Tags { get; set; } = []; + + public static ConversationFilter Empty() + { + return new ConversationFilter(); + } } \ No newline at end of file diff --git a/src/Infrastructure/BotSharp.Abstraction/Repositories/Filters/RoleFilter.cs b/src/Infrastructure/BotSharp.Abstraction/Repositories/Filters/RoleFilter.cs new file mode 100644 index 00000000..0e9e131c --- /dev/null +++ b/src/Infrastructure/BotSharp.Abstraction/Repositories/Filters/RoleFilter.cs @@ -0,0 +1,12 @@ +namespace BotSharp.Abstraction.Repositories.Filters; + +public class RoleFilter +{ + [JsonPropertyName("names")] + public IEnumerable? Names { get; set; } + + public static RoleFilter Empty() + { + return new RoleFilter(); + } +} diff --git a/src/Infrastructure/BotSharp.Abstraction/Users/Models/UserFilter.cs b/src/Infrastructure/BotSharp.Abstraction/Repositories/Filters/UserFilter.cs similarity index 78% rename from src/Infrastructure/BotSharp.Abstraction/Users/Models/UserFilter.cs rename to src/Infrastructure/BotSharp.Abstraction/Repositories/Filters/UserFilter.cs index 73e1794d..6ac35ed2 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Users/Models/UserFilter.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Repositories/Filters/UserFilter.cs @@ -1,4 +1,4 @@ -namespace BotSharp.Abstraction.Users.Models; +namespace BotSharp.Abstraction.Repositories.Filters; public class UserFilter : Pagination { @@ -16,4 +16,9 @@ public class UserFilter : Pagination [JsonPropertyName("sources")] public IEnumerable? Sources { get; set; } + + public static UserFilter Empty() + { + return new UserFilter(); + } } diff --git a/src/Infrastructure/BotSharp.Abstraction/Repositories/IBotSharpRepository.cs b/src/Infrastructure/BotSharp.Abstraction/Repositories/IBotSharpRepository.cs index 57900aa0..910b10c9 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Repositories/IBotSharpRepository.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Repositories/IBotSharpRepository.cs @@ -1,6 +1,7 @@ using BotSharp.Abstraction.Loggers.Models; using BotSharp.Abstraction.Plugins.Models; using BotSharp.Abstraction.Repositories.Filters; +using BotSharp.Abstraction.Roles.Models; using BotSharp.Abstraction.Shared; using BotSharp.Abstraction.Tasks.Models; using BotSharp.Abstraction.Translation.Models; @@ -16,6 +17,12 @@ public interface IBotSharpRepository : IHaveServiceProvider void SavePluginConfig(PluginConfig config); #endregion + #region Role + IEnumerable GetRoles(RoleFilter filter) => throw new NotImplementedException(); + Role? GetRoleDetails(string roleId) => throw new NotImplementedException(); + bool UpdateRole(Role role, bool isUpdateRoleAgents = false) => throw new NotImplementedException(); + #endregion + #region User User? GetUserByEmail(string email) => throw new NotImplementedException(); User? GetUserByPhone(string phone) => throw new NotImplementedException(); @@ -34,6 +41,7 @@ public interface IBotSharpRepository : IHaveServiceProvider void UpdateUserIsDisable(string userId, bool isDisable) => throw new NotImplementedException(); void UpdateUsersIsDisable(List userIds, bool isDisable) => throw new NotImplementedException(); PagedItems GetUsers(UserFilter filter) => throw new NotImplementedException(); + User? GetUserDetails(string userId) => throw new NotImplementedException(); bool UpdateUser(User user, bool isUpdateUserAgents = false) => throw new NotImplementedException(); #endregion diff --git a/src/Infrastructure/BotSharp.Abstraction/Roles/IRoleService.cs b/src/Infrastructure/BotSharp.Abstraction/Roles/IRoleService.cs new file mode 100644 index 00000000..12e45dfd --- /dev/null +++ b/src/Infrastructure/BotSharp.Abstraction/Roles/IRoleService.cs @@ -0,0 +1,12 @@ +using BotSharp.Abstraction.Repositories.Filters; +using BotSharp.Abstraction.Roles.Models; + +namespace BotSharp.Abstraction.Roles; + +public interface IRoleService +{ + Task> GetRoleOptions(); + Task> GetRoles(RoleFilter filter); + Task GetRoleDetails(string roleId); + Task UpdateRole(Role role, bool isUpdateRoleAgents = false); +} diff --git a/src/Infrastructure/BotSharp.Abstraction/Roles/Models/Role.cs b/src/Infrastructure/BotSharp.Abstraction/Roles/Models/Role.cs new file mode 100644 index 00000000..a11029f5 --- /dev/null +++ b/src/Infrastructure/BotSharp.Abstraction/Roles/Models/Role.cs @@ -0,0 +1,22 @@ +namespace BotSharp.Abstraction.Roles.Models; + +public class Role +{ + [JsonPropertyName("id")] + public string Id { get; set; } + + [JsonPropertyName("name")] + public string Name { get; set; } + + [JsonPropertyName("permissions")] + public IEnumerable Permissions { get; set; } = []; + + [JsonIgnore] + public IEnumerable AgentActions { get; set; } = []; + + [JsonPropertyName("updated_time")] + public DateTime UpdatedTime { get; set; } = DateTime.UtcNow; + + [JsonPropertyName("created_time")] + public DateTime CreatedTime { get; set; } = DateTime.UtcNow; +} diff --git a/src/Infrastructure/BotSharp.Abstraction/Roles/Models/RoleAgent.cs b/src/Infrastructure/BotSharp.Abstraction/Roles/Models/RoleAgent.cs new file mode 100644 index 00000000..430a89a1 --- /dev/null +++ b/src/Infrastructure/BotSharp.Abstraction/Roles/Models/RoleAgent.cs @@ -0,0 +1,25 @@ +namespace BotSharp.Abstraction.Roles.Models; + +public class RoleAgent +{ + [JsonPropertyName("id")] + public string Id { get; set; } = string.Empty; + + [JsonPropertyName("user_id")] + public string RoleId { get; set; } = string.Empty; + + [JsonPropertyName("agent_id")] + public string AgentId { get; set; } + + [JsonPropertyName("actions")] + public IEnumerable Actions { get; set; } = []; + + [JsonIgnore] + public Agent? Agent { get; set; } + + [JsonPropertyName("updated_time")] + public DateTime UpdatedTime { get; set; } = DateTime.UtcNow; + + [JsonPropertyName("created_time")] + public DateTime CreatedTime { get; set; } = DateTime.UtcNow; +} diff --git a/src/Infrastructure/BotSharp.Abstraction/Roles/Models/RoleAgentAction.cs b/src/Infrastructure/BotSharp.Abstraction/Roles/Models/RoleAgentAction.cs new file mode 100644 index 00000000..25cc0685 --- /dev/null +++ b/src/Infrastructure/BotSharp.Abstraction/Roles/Models/RoleAgentAction.cs @@ -0,0 +1,16 @@ +namespace BotSharp.Abstraction.Roles.Models; + +public class RoleAgentAction +{ + [JsonPropertyName("id")] + public string Id { get; set; } + + [JsonPropertyName("agent_id")] + public string AgentId { get; set; } + + [JsonIgnore] + public Agent? Agent { get; set; } + + [JsonPropertyName("actions")] + public IEnumerable Actions { get; set; } = []; +} diff --git a/src/Infrastructure/BotSharp.Abstraction/Users/IUserService.cs b/src/Infrastructure/BotSharp.Abstraction/Users/IUserService.cs index 2a869d02..77e40418 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Users/IUserService.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Users/IUserService.cs @@ -1,3 +1,4 @@ +using BotSharp.Abstraction.Repositories.Filters; using BotSharp.Abstraction.Users.Models; using BotSharp.OpenAPI.ViewModels.Users; @@ -7,7 +8,8 @@ public interface IUserService { Task GetUser(string id); Task> GetUsers(UserFilter filter); - Task UpdateUser(User model, bool isUpdateUserAgents = false); + Task GetUserDetails(string userId); + Task UpdateUser(User user, bool isUpdateUserAgents = false); Task CreateUser(User user); Task ActiveUser(UserActivationModel model); Task GetAffiliateToken(string authorization); diff --git a/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Agent.cs b/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Agent.cs index 59da45a5..dadc3532 100644 --- a/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Agent.cs +++ b/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Agent.cs @@ -1,7 +1,4 @@ -using BotSharp.Abstraction.Agents.Models; using BotSharp.Abstraction.Routing.Models; -using BotSharp.Abstraction.Users.Models; -using Microsoft.Extensions.Logging; using System.IO; namespace BotSharp.Core.Repository @@ -358,6 +355,11 @@ namespace BotSharp.Core.Repository public List GetAgents(AgentFilter filter) { + if (filter == null) + { + filter = AgentFilter.Empty(); + } + var query = Agents; if (!string.IsNullOrEmpty(filter.AgentName)) { diff --git a/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.AgentTask.cs b/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.AgentTask.cs index 776cf137..f855544b 100644 --- a/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.AgentTask.cs +++ b/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.AgentTask.cs @@ -8,6 +8,11 @@ public partial class FileRepository #region Task public PagedItems GetAgentTasks(AgentTaskFilter filter) { + if (filter == null) + { + filter = AgentTaskFilter.Empty(); + } + var tasks = new List(); var pager = filter.Pager ?? new Pagination(); var skipCount = 0; diff --git a/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Conversation.cs b/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Conversation.cs index 28d0a6cc..9f5ed57f 100644 --- a/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Conversation.cs +++ b/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Conversation.cs @@ -322,6 +322,11 @@ namespace BotSharp.Core.Repository public PagedItems GetConversations(ConversationFilter filter) { + if (filter == null) + { + filter = ConversationFilter.Empty(); + } + var records = new List(); var dir = Path.Combine(_dbSettings.FileRepository, _conversationSettings.DataDir); var pager = filter?.Pager ?? new Pagination(); diff --git a/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Role.cs b/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Role.cs new file mode 100644 index 00000000..13036a91 --- /dev/null +++ b/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Role.cs @@ -0,0 +1,97 @@ +using BotSharp.Abstraction.Users.Models; +using System.IO; + +namespace BotSharp.Core.Repository; + +public partial class FileRepository +{ + public IEnumerable GetRoles(RoleFilter filter) + { + var roles = Roles; + if (filter == null) + { + filter = RoleFilter.Empty(); + } + + // Apply filters + if (!filter.Names.IsNullOrEmpty()) + { + roles = roles.Where(x => filter.Names.Contains(x.Id)); + } + + return roles.ToList(); + } + + public Role? GetRoleDetails(string roleId) + { + if (string.IsNullOrWhiteSpace(roleId)) return null; + + var role = Roles.FirstOrDefault(x => x.Id == roleId); + if (role == null) return null; + + var agentActions = new List(); + var roleAgents = RoleAgents?.Where(x => x.RoleId == roleId)?.ToList() ?? []; + var agentIds = roleAgents.Select(x => x.AgentId).Distinct().ToList(); + + if (!agentIds.IsNullOrEmpty()) + { + var agents = GetAgents(new AgentFilter { AgentIds = agentIds }); + + foreach (var item in roleAgents) + { + var found = agents.FirstOrDefault(x => x.Id == item.AgentId); + if (found == null) continue; + + agentActions.Add(new RoleAgentAction + { + Id = item.Id, + AgentId = found.Id, + Agent = found, + Actions = item.Actions + }); + } + } + + role.AgentActions = agentActions; + return role; + } + + public bool UpdateRole(Role role, bool isUpdateRoleAgents = false) + { + if (string.IsNullOrEmpty(role?.Id) || string.IsNullOrEmpty(role?.Name)) + { + return false; + } + + var dir = Path.Combine(_dbSettings.FileRepository, ROLES_FOLDER, role.Id); + if (!Directory.Exists(dir)) + { + Directory.CreateDirectory(dir); + } + + var roleFile = Path.Combine(dir, ROLE_FILE); + role.CreatedTime = DateTime.UtcNow; + role.UpdatedTime = DateTime.UtcNow; + File.WriteAllText(roleFile, JsonSerializer.Serialize(role, _options)); + + if (isUpdateRoleAgents) + { + var roleAgents = role.AgentActions?.Select(x => new RoleAgent + { + Id = !string.IsNullOrEmpty(x.Id) ? x.Id : Guid.NewGuid().ToString(), + RoleId = role.Id, + AgentId = x.AgentId, + Actions = x.Actions ?? [], + CreatedTime = DateTime.UtcNow, + UpdatedTime = DateTime.UtcNow + })?.ToList() ?? []; + + var roleAgentFile = Path.Combine(dir, ROLE_AGENT_FILE); + File.WriteAllText(roleAgentFile, JsonSerializer.Serialize(roleAgents, _options)); + _roleAgents = []; + } + + _roles = []; + return true; + } +} diff --git a/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.User.cs b/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.User.cs index 80ccacc4..c55f248c 100644 --- a/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.User.cs +++ b/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.User.cs @@ -73,6 +73,11 @@ public partial class FileRepository public PagedItems GetUsers(UserFilter filter) { + if (filter == null) + { + filter = UserFilter.Empty(); + } + var users = Users; // Apply filters @@ -97,34 +102,6 @@ public partial class FileRepository users = users.Where(x => filter.Sources.Contains(x.Source)); } - // Get user agents - var userIds = users.Select(x => x.Id).ToList(); - var userAgents = UserAgents.Where(x => userIds.Contains(x.UserId)).ToList(); - var agentIds = userAgents?.Select(x => x.AgentId)?.Distinct()?.ToList() ?? []; - - if (!agentIds.IsNullOrEmpty()) - { - var agents = GetAgents(new AgentFilter { AgentIds = agentIds }); - foreach (var item in userAgents) - { - item.Agent = agents.FirstOrDefault(x => x.Id == item.AgentId); - } - - foreach (var user in users) - { - var found = userAgents.Where(x => x.UserId == user.Id).ToList(); - if (found.IsNullOrEmpty()) continue; - - user.AgentActions = found.Select(x => new UserAgentAction - { - Id = x.Id, - AgentId = x.AgentId, - Agent = x.Agent, - Actions = x.Actions - }); - } - } - return new PagedItems { Items = users.OrderByDescending(x => x.CreatedTime).Skip(filter.Offset).Take(filter.Size), @@ -132,6 +109,40 @@ public partial class FileRepository }; } + public User? GetUserDetails(string userId) + { + if (string.IsNullOrWhiteSpace(userId)) return null; + + var user = Users.FirstOrDefault(x => x.Id == userId); + if (user == null) return null; + + var agentActions = new List(); + var userAgents = UserAgents?.Where(x => x.UserId == userId)?.ToList() ?? []; + var agentIds = userAgents.Select(x => x.AgentId)?.Distinct().ToList(); + + if (!agentIds.IsNullOrEmpty()) + { + var agents = GetAgents(new AgentFilter { AgentIds = agentIds }); + + foreach (var item in userAgents) + { + var found = agents.FirstOrDefault(x => x.Id == item.AgentId); + if (found == null) continue; + + agentActions.Add(new UserAgentAction + { + Id = item.Id, + AgentId = found.Id, + Agent = found, + Actions = item.Actions ?? [] + }); + } + } + + user.AgentActions = agentActions; + return user; + } + public bool UpdateUser(User user, bool isUpdateUserAgents = false) { if (string.IsNullOrEmpty(user?.Id)) return false; @@ -160,10 +171,10 @@ public partial class FileRepository var userAgentFile = Path.Combine(dir, USER_AGENT_FILE); File.WriteAllText(userAgentFile, JsonSerializer.Serialize(userAgents, _options)); + _userAgents = []; } _users = []; - _userAgents = []; return true; } } diff --git a/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.cs b/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.cs index f3e1fddf..0edcb699 100644 --- a/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.cs +++ b/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.cs @@ -22,30 +22,38 @@ public partial class FileRepository : IBotSharpRepository private const string AGENT_FILE = "agent.json"; private const string AGENT_INSTRUCTION_FILE = "instruction"; private const string AGENT_SAMPLES_FILE = "samples.txt"; - private const string USER_FILE = "user.json"; - private const string USER_AGENT_FILE = "agents.json"; - private const string CONVERSATION_FILE = "conversation.json"; - private const string STATS_FILE = "stats.json"; - private const string DIALOG_FILE = "dialogs.json"; - private const string STATE_FILE = "state.json"; - private const string BREAKPOINT_FILE = "breakpoint.json"; - private const string EXECUTION_LOG_FILE = "execution.log"; - private const string PLUGIN_CONFIG_FILE = "config.json"; - private const string AGENT_TASK_PREFIX = "#metadata"; - private const string AGENT_TASK_SUFFIX = "/metadata"; - private const string TRANSLATION_MEMORY_FILE = "memory.json"; private const string AGENT_INSTRUCTIONS_FOLDER = "instructions"; private const string AGENT_FUNCTIONS_FOLDER = "functions"; private const string AGENT_TEMPLATES_FOLDER = "templates"; private const string AGENT_RESPONSES_FOLDER = "responses"; private const string AGENT_TASKS_FOLDER = "tasks"; + private const string AGENT_TASK_PREFIX = "#metadata"; + private const string AGENT_TASK_SUFFIX = "/metadata"; + + private const string CONVERSATION_FILE = "conversation.json"; + private const string DIALOG_FILE = "dialogs.json"; + private const string STATE_FILE = "state.json"; + private const string BREAKPOINT_FILE = "breakpoint.json"; + private const string TRANSLATION_MEMORY_FILE = "memory.json"; + private const string USERS_FOLDER = "users"; + private const string USER_FILE = "user.json"; + private const string USER_AGENT_FILE = "agents.json"; + + private const string ROLES_FOLDER = "roles"; + private const string ROLE_FILE = "role.json"; + private const string ROLE_AGENT_FILE = "agents.json"; + private const string KNOWLEDGE_FOLDER = "knowledgebase"; private const string VECTOR_FOLDER = "vector"; private const string COLLECTION_CONFIG_FILE = "collection-config.json"; private const string KNOWLEDGE_DOC_FOLDER = "document"; private const string KNOWLEDGE_DOC_META_FILE = "meta.json"; + private const string EXECUTION_LOG_FILE = "execution.log"; + private const string PLUGIN_CONFIG_FILE = "config.json"; + private const string STATS_FILE = "stats.json"; + public FileRepository( IServiceProvider services, BotSharpDatabaseSettings dbSettings, @@ -73,12 +81,68 @@ public partial class FileRepository : IBotSharpRepository _dbSettings.FileRepository = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, _dbSettings.FileRepository); } + private List _roles = new List(); private List _users = new List(); private List _agents = new List(); + private List _roleAgents = new List(); private List _userAgents = new List(); private List _conversations = new List(); private PluginConfig? _pluginConfig = null; + private IQueryable Roles + { + get + { + if (!_roles.IsNullOrEmpty()) + { + return _roles.AsQueryable(); + } + + var dir = Path.Combine(_dbSettings.FileRepository, ROLES_FOLDER); + _roles = new List(); + if (Directory.Exists(dir)) + { + foreach (var d in Directory.GetDirectories(dir)) + { + var roleFile = Path.Combine(d, ROLE_FILE); + if (!Directory.Exists(d) || !File.Exists(roleFile)) + continue; + + var json = File.ReadAllText(roleFile); + _roles.Add(JsonSerializer.Deserialize(json, _options)); + } + } + return _roles.AsQueryable(); + } + } + + private IQueryable RoleAgents + { + get + { + if (!_roleAgents.IsNullOrEmpty()) + { + return _roleAgents.AsQueryable(); + } + + var dir = Path.Combine(_dbSettings.FileRepository, ROLES_FOLDER); + _roleAgents = new List(); + if (Directory.Exists(dir)) + { + foreach (var d in Directory.GetDirectories(dir)) + { + var file = Path.Combine(d, ROLE_AGENT_FILE); + if (!Directory.Exists(d) || !File.Exists(file)) + continue; + + var json = File.ReadAllText(file); + _roleAgents.AddRange(JsonSerializer.Deserialize>(json, _options)); + } + } + return _roleAgents.AsQueryable(); + } + } + private IQueryable Users { get diff --git a/src/Infrastructure/BotSharp.Core/Roles/Services/RoleService.cs b/src/Infrastructure/BotSharp.Core/Roles/Services/RoleService.cs new file mode 100644 index 00000000..b9f04cfe --- /dev/null +++ b/src/Infrastructure/BotSharp.Core/Roles/Services/RoleService.cs @@ -0,0 +1,56 @@ +using BotSharp.Abstraction.Users.Enums; +using System.Reflection; + +namespace BotSharp.Core.Roles.Services; + +public class RoleService : IRoleService +{ + private readonly IServiceProvider _services; + private readonly ILogger _logger; + + public RoleService( + IServiceProvider services, + ILogger logger) + { + _services = services; + _logger = logger; + } + + public async Task> GetRoleOptions() + { + var fields = typeof(UserRole).GetFields(BindingFlags.Public | BindingFlags.Static) + .Where(x => x.IsLiteral && !x.IsInitOnly).ToList(); + + return fields.Select(x => x.GetValue(null)?.ToString()) + .Where(x => !string.IsNullOrWhiteSpace(x)) + .Distinct() + .ToList(); + } + + public async Task> GetRoles(RoleFilter filter) + { + var db = _services.GetRequiredService(); + var roles = db.GetRoles(filter); + return roles; + } + + public async Task GetRoleDetails(string roleId) + { + var db = _services.GetRequiredService(); + var role = db.GetRoleDetails(roleId); + return role; + } + + public async Task UpdateRole(Role role, bool isUpdateRoleAgents = false) + { + if (role == null) return false; + + if (string.IsNullOrEmpty(role.Id)) + { + role.Id = Guid.NewGuid().ToString(); + } + + var db = _services.GetRequiredService(); + return db.UpdateRole(role, isUpdateRoleAgents); + } +} diff --git a/src/Infrastructure/BotSharp.Core/Users/Services/UserService.cs b/src/Infrastructure/BotSharp.Core/Users/Services/UserService.cs index daf8a340..9fa2b73a 100644 --- a/src/Infrastructure/BotSharp.Core/Users/Services/UserService.cs +++ b/src/Infrastructure/BotSharp.Core/Users/Services/UserService.cs @@ -407,10 +407,18 @@ public class UserService : IUserService return users; } - public async Task UpdateUser(User model, bool isUpdateUserAgents = false) + public async Task GetUserDetails(string userId) { var db = _services.GetRequiredService(); - return db.UpdateUser(model, isUpdateUserAgents); + return db.GetUserDetails(userId); + } + + public async Task UpdateUser(User user, bool isUpdateUserAgents = false) + { + if (user == null) return false; + + var db = _services.GetRequiredService(); + return db.UpdateUser(user, isUpdateUserAgents); } public async Task ActiveUser(UserActivationModel model) diff --git a/src/Infrastructure/BotSharp.Core/Using.cs b/src/Infrastructure/BotSharp.Core/Using.cs index 8a0ca2af..c486a042 100644 --- a/src/Infrastructure/BotSharp.Core/Using.cs +++ b/src/Infrastructure/BotSharp.Core/Using.cs @@ -16,6 +16,8 @@ global using BotSharp.Abstraction.Agents; global using BotSharp.Abstraction.Conversations; global using BotSharp.Abstraction.Knowledges; global using BotSharp.Abstraction.Users; +global using BotSharp.Abstraction.Roles; +global using BotSharp.Abstraction.Roles.Models; global using BotSharp.Abstraction.Utilities; global using BotSharp.Abstraction.Conversations.Models; global using BotSharp.Abstraction.Agents.Settings; diff --git a/src/Infrastructure/BotSharp.OpenAPI/Controllers/RoleController.cs b/src/Infrastructure/BotSharp.OpenAPI/Controllers/RoleController.cs new file mode 100644 index 00000000..5eedc928 --- /dev/null +++ b/src/Infrastructure/BotSharp.OpenAPI/Controllers/RoleController.cs @@ -0,0 +1,64 @@ +using BotSharp.Abstraction.Roles; +using BotSharp.Abstraction.Users.Enums; + +namespace BotSharp.OpenAPI.Controllers; + +[Authorize] +[ApiController] +public class RoleController : ControllerBase +{ + private readonly IServiceProvider _services; + private readonly IRoleService _roleService; + private readonly IUserIdentity _user; + + public RoleController( + IServiceProvider services, + IRoleService roleService, + IUserIdentity user) + { + _services = services; + _roleService = roleService; + _user = user; + } + + [HttpGet("/role/options")] + public async Task> GetRoleOptions() + { + return await _roleService.GetRoleOptions(); + } + + [HttpPost("/roles")] + public async Task> GetRoles([FromBody] RoleFilter? filter = null) + { + if (filter == null) + { + filter = RoleFilter.Empty(); + } + + var roles = await _roleService.GetRoles(filter); + return roles.Select(x => RoleViewModel.FromRole(x)).ToList(); + } + + [HttpGet("/role/{id}/details")] + public async Task GetRoleDetails([FromRoute] string id) + { + var role = await _roleService.GetRoleDetails(id); + return RoleViewModel.FromRole(role); + } + + [HttpPut("/role")] + public async Task UpdateRole([FromBody] RoleUpdateModel model) + { + if (model == null) return false; + + var userService = _services.GetRequiredService(); + var user = await userService.GetUser(_user.Id); + if (user == null || !UserConstant.AdminRoles.Contains(user.Role)) + { + return false; + } + + var role = RoleUpdateModel.ToRole(model); + return await _roleService.UpdateRole(role, isUpdateRoleAgents: true); + } +} diff --git a/src/Infrastructure/BotSharp.OpenAPI/Controllers/UserController.cs b/src/Infrastructure/BotSharp.OpenAPI/Controllers/UserController.cs index 90bf20f1..366bb91b 100644 --- a/src/Infrastructure/BotSharp.OpenAPI/Controllers/UserController.cs +++ b/src/Infrastructure/BotSharp.OpenAPI/Controllers/UserController.cs @@ -198,6 +198,13 @@ public class UserController : ControllerBase }; } + [HttpGet("/user/{id}/details")] + public async Task GetUserDetails(string id) + { + var userService = _services.GetRequiredService(); + var user = await userService.GetUserDetails(id); + return UserViewModel.FromUser(user); + } [HttpPut("/user")] public async Task UpdateUser([FromBody] UserUpdateModel model) diff --git a/src/Infrastructure/BotSharp.OpenAPI/Using.cs b/src/Infrastructure/BotSharp.OpenAPI/Using.cs index 3542cef9..602f4437 100644 --- a/src/Infrastructure/BotSharp.OpenAPI/Using.cs +++ b/src/Infrastructure/BotSharp.OpenAPI/Using.cs @@ -32,3 +32,4 @@ global using BotSharp.OpenAPI.ViewModels.Conversations; global using BotSharp.OpenAPI.ViewModels.Users; global using BotSharp.OpenAPI.ViewModels.Agents; global using BotSharp.OpenAPI.ViewModels.Files; +global using BotSharp.OpenAPI.ViewModels.Roles; \ No newline at end of file diff --git a/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Roles/RoleAgentActionViewModel.cs b/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Roles/RoleAgentActionViewModel.cs new file mode 100644 index 00000000..ffd7987e --- /dev/null +++ b/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Roles/RoleAgentActionViewModel.cs @@ -0,0 +1,42 @@ +using BotSharp.Abstraction.Agents.Models; +using BotSharp.Abstraction.Roles.Models; +using System.Text.Json.Serialization; + +namespace BotSharp.OpenAPI.ViewModels.Roles; + +public class RoleAgentActionViewModel +{ + [JsonPropertyName("id")] + public string? Id { get; set; } + + [JsonPropertyName("agent_id")] + public string AgentId { get; set; } + + [JsonPropertyName("agent")] + public Agent? Agent { get; set; } + + [JsonPropertyName("actions")] + public IEnumerable Actions { get; set; } = []; + + + public static RoleAgentActionViewModel ToViewModel(RoleAgentAction action) + { + return new RoleAgentActionViewModel + { + Id = action.Id, + AgentId = action.AgentId, + Agent = action.Agent, + Actions = action.Actions + }; + } + + public static RoleAgentAction ToDomainModel(RoleAgentActionViewModel action) + { + return new RoleAgentAction + { + Id = action.Id, + AgentId = action.AgentId, + Actions = action.Actions + }; + } +} diff --git a/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Roles/RoleUpdateModel.cs b/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Roles/RoleUpdateModel.cs new file mode 100644 index 00000000..eece96fc --- /dev/null +++ b/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Roles/RoleUpdateModel.cs @@ -0,0 +1,30 @@ +using BotSharp.Abstraction.Roles.Models; +using System.Text.Json.Serialization; + +namespace BotSharp.OpenAPI.ViewModels.Roles; + +public class RoleUpdateModel +{ + [JsonPropertyName("id")] + public string? Id { get; set; } + + [JsonPropertyName("name")] + public string Name { get; set; } = null!; + + [JsonPropertyName("permissions")] + public IEnumerable Permissions { get; set; } = []; + + [JsonPropertyName("agent_actions")] + public IEnumerable AgentActions { get; set; } = []; + + public static Role ToRole(RoleUpdateModel model) + { + return new Role + { + Id = model.Id, + Name = model.Name, + Permissions = model.Permissions, + AgentActions = model.AgentActions?.Select(x => RoleAgentActionViewModel.ToDomainModel(x)) ?? [] + }; + } +} diff --git a/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Roles/RoleViewModel.cs b/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Roles/RoleViewModel.cs new file mode 100644 index 00000000..e060b482 --- /dev/null +++ b/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Roles/RoleViewModel.cs @@ -0,0 +1,40 @@ +using BotSharp.Abstraction.Roles.Models; +using System.Text.Json.Serialization; + +namespace BotSharp.OpenAPI.ViewModels.Roles; + +public class RoleViewModel +{ + [JsonPropertyName("id")] + public string? Id { get; set; } + + [JsonPropertyName("name")] + public string Name { get; set; } = null!; + + [JsonPropertyName("permissions")] + public IEnumerable Permissions { get; set; } = []; + + [JsonPropertyName("agent_actions")] + public IEnumerable AgentActions { get; set; } = []; + + [JsonPropertyName("create_date")] + public DateTime CreateDate { get; set; } + + [JsonPropertyName("update_date")] + public DateTime UpdateDate { get; set; } + + public static RoleViewModel FromRole(Role? role) + { + if (role == null) return null; + + return new RoleViewModel + { + Id = role.Id, + Name = role.Name, + Permissions = role.Permissions, + AgentActions = role.AgentActions?.Select(x => RoleAgentActionViewModel.ToViewModel(x)) ?? [], + CreateDate = role.CreatedTime, + UpdateDate = role.UpdatedTime + }; + } +} diff --git a/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.Agent.cs b/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.Agent.cs index 44a4e2bc..b0146903 100644 --- a/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.Agent.cs +++ b/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.Agent.cs @@ -283,6 +283,11 @@ public partial class MongoRepository public List GetAgents(AgentFilter filter) { + if (filter == null) + { + filter = AgentFilter.Empty(); + } + var agents = new List(); var builder = Builders.Filter; var filters = new List>() { builder.Empty }; diff --git a/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.AgentTask.cs b/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.AgentTask.cs index a00858c4..2946b7b0 100644 --- a/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.AgentTask.cs +++ b/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.AgentTask.cs @@ -8,6 +8,11 @@ public partial class MongoRepository #region Task public PagedItems GetAgentTasks(AgentTaskFilter filter) { + if (filter == null) + { + filter = AgentTaskFilter.Empty(); + } + var pager = filter.Pager ?? new Pagination(); var builder = Builders.Filter; var filters = new List>() { builder.Empty }; diff --git a/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.Conversation.cs b/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.Conversation.cs index 85fa1033..ccbfc1af 100644 --- a/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.Conversation.cs +++ b/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.Conversation.cs @@ -281,6 +281,11 @@ public partial class MongoRepository public PagedItems GetConversations(ConversationFilter filter) { + if (filter == null) + { + filter = ConversationFilter.Empty(); + } + var convBuilder = Builders.Filter; var convFilters = new List>() { convBuilder.Empty }; diff --git a/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.User.cs b/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.User.cs index 19d933a9..d7115c36 100644 --- a/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.User.cs +++ b/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.User.cs @@ -2,6 +2,8 @@ using BotSharp.Abstraction.Agents.Models; using BotSharp.Abstraction.Repositories.Filters; using BotSharp.Abstraction.Users.Enums; using BotSharp.Abstraction.Users.Models; +using MongoDB.Driver; +using System.Globalization; namespace BotSharp.Plugin.MongoStorage.Repository; @@ -173,6 +175,11 @@ public partial class MongoRepository public PagedItems GetUsers(UserFilter filter) { + if (filter == null) + { + filter = UserFilter.Empty(); + } + var userBuilder = Builders.Filter; var userFilters = new List>() { userBuilder.Empty }; @@ -207,44 +214,6 @@ public partial class MongoRepository var count = _dc.Users.CountDocuments(filterDef); var users = userDocs.Select(x => x.ToUser()).ToList(); - var userIds = users.Select(x => x.Id).ToList(); - var userAgents = _dc.UserAgents.AsQueryable().Where(x => userIds.Contains(x.UserId)).Select(x => new UserAgent - { - Id = x.Id, - UserId = x.UserId, - AgentId = x.AgentId, - Actions = x.Actions ?? Enumerable.Empty(), - CreatedTime = x.CreatedTime, - UpdatedTime = x.UpdatedTime - }).ToList(); - var agentIds = userAgents.Select(x => x.AgentId).Distinct().ToList(); - - if (!agentIds.IsNullOrEmpty()) - { - var agents = GetAgents(new AgentFilter { AgentIds = agentIds }); - foreach (var item in userAgents) - { - var agent = agents.FirstOrDefault(x => x.Id == item.AgentId); - if (agent == null) continue; - - item.Agent = agent; - } - - foreach (var user in users) - { - var found = userAgents.Where(x => x.UserId == user.Id).ToList(); - if (found.IsNullOrEmpty()) continue; - - user.AgentActions = found.Select(x => new UserAgentAction - { - Id = x.Id, - AgentId = x.AgentId, - Agent = x.Agent, - Actions = x.Actions - }); - } - } - return new PagedItems { Items = users, @@ -252,6 +221,48 @@ public partial class MongoRepository }; } + public User? GetUserDetails(string userId) + { + if (string.IsNullOrWhiteSpace(userId)) return null; + + var userDoc = _dc.Users.Find(Builders.Filter.Eq(x => x.Id, userId)).FirstOrDefault(); + if (userDoc == null) return null; + + var user = userDoc.ToUser(); + + var userAgents = _dc.UserAgents.AsQueryable().Where(x => x.UserId == userId).Select(x => new UserAgent + { + Id = x.Id, + UserId = x.UserId, + AgentId = x.AgentId, + Actions = x.Actions ?? Enumerable.Empty() + }).ToList(); + + var agentActions = new List(); + var agentIds = userAgents.Select(x => x.AgentId)?.Distinct().ToList(); + + if (!agentIds.IsNullOrEmpty()) + { + var agents = GetAgents(new AgentFilter { AgentIds = agentIds }); + + foreach (var item in userAgents) + { + var found = agents.FirstOrDefault(x => x.Id == item.AgentId); + if (found == null) continue; + + agentActions.Add(new UserAgentAction + { + Id = item.Id, + AgentId = found.Id, + Agent = found, + Actions = item.Actions + }); + } + } + + user.AgentActions = agentActions; + return user; + } public bool UpdateUser(User user, bool isUpdateUserAgents = false) { From f9466ab3986db2088e6e576bca6d42ddc1af2a30 Mon Sep 17 00:00:00 2001 From: YouWeiDH Date: Thu, 14 Nov 2024 20:42:25 +0800 Subject: [PATCH 16/29] hdong: fix admin account conflict with common account. --- .../Repositories/IBotSharpRepository.cs | 1 + .../BotSharp.Core/Users/Services/UserService.cs | 2 +- .../Repository/MongoRepository.User.cs | 17 ++++++++++++++++- 3 files changed, 18 insertions(+), 2 deletions(-) diff --git a/src/Infrastructure/BotSharp.Abstraction/Repositories/IBotSharpRepository.cs b/src/Infrastructure/BotSharp.Abstraction/Repositories/IBotSharpRepository.cs index 21b6bb8d..9282506d 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Repositories/IBotSharpRepository.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Repositories/IBotSharpRepository.cs @@ -19,6 +19,7 @@ public interface IBotSharpRepository : IHaveServiceProvider #region User User? GetUserByEmail(string email) => throw new NotImplementedException(); User? GetUserByPhone(string phone, string regionCode = "CN") => throw new NotImplementedException(); + User? GetAdminUserByPhone(string phone, string regionCode = "CN") => throw new NotImplementedException(); User? GetAffiliateUserByPhone(string phone) => throw new NotImplementedException(); User? GetUserById(string id) => throw new NotImplementedException(); List GetUserByIds(List ids) => throw new NotImplementedException(); diff --git a/src/Infrastructure/BotSharp.Core/Users/Services/UserService.cs b/src/Infrastructure/BotSharp.Core/Users/Services/UserService.cs index fa757e2f..ba97bae8 100644 --- a/src/Infrastructure/BotSharp.Core/Users/Services/UserService.cs +++ b/src/Infrastructure/BotSharp.Core/Users/Services/UserService.cs @@ -172,7 +172,7 @@ public class UserService : IUserService var base64 = Encoding.UTF8.GetString(Convert.FromBase64String(authorization)); var (id, password) = base64.SplitAsTuple(":"); var db = _services.GetRequiredService(); - var record = db.GetUserByPhone(id); + var record = db.GetAdminUserByPhone(id); var isCanLogin = record != null && !record.IsDisabled && record.Type == UserType.Internal && new List { diff --git a/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.User.cs b/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.User.cs index 4af170b0..bfaecbf5 100644 --- a/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.User.cs +++ b/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.User.cs @@ -22,7 +22,22 @@ public partial class MongoRepository string phoneSecond = phone.StartsWith("+86") ? phone.Replace("+86", "") : $"+86{phone}"; - var user = _dc.Users.AsQueryable().FirstOrDefault(x => (x.Phone == phone || x.Phone == phoneSecond) && x.Type != UserType.Affiliate && (x.RegionCode == regionCode || string.IsNullOrWhiteSpace(x.RegionCode))); + var user = _dc.Users.AsQueryable().FirstOrDefault(x => (x.Phone == phone || x.Phone == phoneSecond) && x.Type != UserType.Affiliate + && (x.RegionCode == regionCode || string.IsNullOrWhiteSpace(x.RegionCode))); + return user != null ? user.ToUser() : null; + } + + public User? GetAdminUserByPhone(string phone, string regionCode = "CN") + { + if (string.IsNullOrWhiteSpace(phone)) + { + return null; + } + + string phoneSecond = phone.StartsWith("+86") ? phone.Replace("+86", "") : $"+86{phone}"; + + var user = _dc.Users.AsQueryable().FirstOrDefault(x => (x.Phone == phone || x.Phone == phoneSecond) && x.Type != UserType.Affiliate + && (x.RegionCode == regionCode || string.IsNullOrWhiteSpace(x.RegionCode)) && (x.Role == "admin" || x.Role == "root")); return user != null ? user.ToUser() : null; } From dda5c2a211a8a33dfc42107a0dd6faadc4913890 Mon Sep 17 00:00:00 2001 From: Jicheng Lu <103353@smsassist.com> Date: Thu, 14 Nov 2024 17:33:26 -0600 Subject: [PATCH 17/29] add role --- .../Repositories/Filters/UserFilter.cs | 3 + .../Repositories/IBotSharpRepository.cs | 9 +- .../Roles/IRoleService.cs | 1 + .../BotSharp.Abstraction/Roles/Models/Role.cs | 4 +- .../Roles/Models/RoleAgent.cs | 4 +- .../Users/Enums/UserAction.cs | 2 + .../Users/Enums/UserRole.cs | 14 +- .../Users/IUserService.cs | 2 + .../Users/Models/UserAuthorization.cs | 8 + .../Services/AgentService.CreateAgent.cs | 7 +- .../Services/AgentService.DeleteAgent.cs | 7 +- .../Services/AgentService.RefreshAgents.cs | 5 +- .../Services/AgentService.UpdateAgent.cs | 7 +- .../BotSharp.Core/BotSharpCoreExtensions.cs | 2 + .../FileRepository/FileRepository.Agent.cs | 21 ++- .../FileRepository/FileRepository.Role.cs | 51 +++++- .../FileRepository/FileRepository.User.cs | 28 +++- .../Roles/Services/RoleService.cs | 11 +- .../Users/Services/UserService.cs | 46 +++++- .../Controllers/AgentController.cs | 15 +- .../Controllers/PluginController.cs | 17 +- .../Controllers/RoleController.cs | 30 +++- .../Controllers/UserController.cs | 16 +- .../ViewModels/Roles/RoleViewModel.cs | 8 +- .../Collections/RoleAgentDocument.cs | 25 +++ .../Collections/RoleDocument.cs | 24 +++ .../MongoDbContext.cs | 6 + .../Repository/MongoRepository.Agent.cs | 8 +- .../Repository/MongoRepository.Role.cs | 152 ++++++++++++++++++ .../Repository/MongoRepository.User.cs | 28 +++- 30 files changed, 481 insertions(+), 80 deletions(-) create mode 100644 src/Infrastructure/BotSharp.Abstraction/Users/Models/UserAuthorization.cs create mode 100644 src/Plugins/BotSharp.Plugin.MongoStorage/Collections/RoleAgentDocument.cs create mode 100644 src/Plugins/BotSharp.Plugin.MongoStorage/Collections/RoleDocument.cs create mode 100644 src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.Role.cs diff --git a/src/Infrastructure/BotSharp.Abstraction/Repositories/Filters/UserFilter.cs b/src/Infrastructure/BotSharp.Abstraction/Repositories/Filters/UserFilter.cs index 6ac35ed2..4c0d259c 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Repositories/Filters/UserFilter.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Repositories/Filters/UserFilter.cs @@ -14,6 +14,9 @@ public class UserFilter : Pagination [JsonPropertyName("roles")] public IEnumerable? Roles { get; set; } + [JsonPropertyName("types")] + public IEnumerable? Types { get; set; } + [JsonPropertyName("sources")] public IEnumerable? Sources { get; set; } diff --git a/src/Infrastructure/BotSharp.Abstraction/Repositories/IBotSharpRepository.cs b/src/Infrastructure/BotSharp.Abstraction/Repositories/IBotSharpRepository.cs index 910b10c9..0958b4f4 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Repositories/IBotSharpRepository.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Repositories/IBotSharpRepository.cs @@ -18,9 +18,10 @@ public interface IBotSharpRepository : IHaveServiceProvider #endregion #region Role + bool RefreshRoles(IEnumerable roles) => throw new NotImplementedException(); IEnumerable GetRoles(RoleFilter filter) => throw new NotImplementedException(); - Role? GetRoleDetails(string roleId) => throw new NotImplementedException(); - bool UpdateRole(Role role, bool isUpdateRoleAgents = false) => throw new NotImplementedException(); + Role? GetRoleDetails(string roleId, bool includeAgent = false) => throw new NotImplementedException(); + bool UpdateRole(Role role, bool updateRoleAgents = false) => throw new NotImplementedException(); #endregion #region User @@ -41,8 +42,8 @@ public interface IBotSharpRepository : IHaveServiceProvider void UpdateUserIsDisable(string userId, bool isDisable) => throw new NotImplementedException(); void UpdateUsersIsDisable(List userIds, bool isDisable) => throw new NotImplementedException(); PagedItems GetUsers(UserFilter filter) => throw new NotImplementedException(); - User? GetUserDetails(string userId) => throw new NotImplementedException(); - bool UpdateUser(User user, bool isUpdateUserAgents = false) => throw new NotImplementedException(); + User? GetUserDetails(string userId, bool includeAgent = false) => throw new NotImplementedException(); + bool UpdateUser(User user, bool updateUserAgents = false) => throw new NotImplementedException(); #endregion #region Agent diff --git a/src/Infrastructure/BotSharp.Abstraction/Roles/IRoleService.cs b/src/Infrastructure/BotSharp.Abstraction/Roles/IRoleService.cs index 12e45dfd..325c2e9c 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Roles/IRoleService.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Roles/IRoleService.cs @@ -5,6 +5,7 @@ namespace BotSharp.Abstraction.Roles; public interface IRoleService { + Task RefreshRoles(); Task> GetRoleOptions(); Task> GetRoles(RoleFilter filter); Task GetRoleDetails(string roleId); diff --git a/src/Infrastructure/BotSharp.Abstraction/Roles/Models/Role.cs b/src/Infrastructure/BotSharp.Abstraction/Roles/Models/Role.cs index a11029f5..a0c11bc3 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Roles/Models/Role.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Roles/Models/Role.cs @@ -15,8 +15,8 @@ public class Role public IEnumerable AgentActions { get; set; } = []; [JsonPropertyName("updated_time")] - public DateTime UpdatedTime { get; set; } = DateTime.UtcNow; + public DateTime UpdatedTime { get; set; } [JsonPropertyName("created_time")] - public DateTime CreatedTime { get; set; } = DateTime.UtcNow; + public DateTime CreatedTime { get; set; } } diff --git a/src/Infrastructure/BotSharp.Abstraction/Roles/Models/RoleAgent.cs b/src/Infrastructure/BotSharp.Abstraction/Roles/Models/RoleAgent.cs index 430a89a1..8b84591c 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Roles/Models/RoleAgent.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Roles/Models/RoleAgent.cs @@ -18,8 +18,8 @@ public class RoleAgent public Agent? Agent { get; set; } [JsonPropertyName("updated_time")] - public DateTime UpdatedTime { get; set; } = DateTime.UtcNow; + public DateTime UpdatedTime { get; set; } [JsonPropertyName("created_time")] - public DateTime CreatedTime { get; set; } = DateTime.UtcNow; + public DateTime CreatedTime { get; set; } } diff --git a/src/Infrastructure/BotSharp.Abstraction/Users/Enums/UserAction.cs b/src/Infrastructure/BotSharp.Abstraction/Users/Enums/UserAction.cs index 4838e757..b565a260 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Users/Enums/UserAction.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Users/Enums/UserAction.cs @@ -4,4 +4,6 @@ public static class UserAction { public const string Edit = "edit"; public const string Chat = "chat"; + public const string Train = "train"; + public const string Evaluate = "evaluate"; } diff --git a/src/Infrastructure/BotSharp.Abstraction/Users/Enums/UserRole.cs b/src/Infrastructure/BotSharp.Abstraction/Users/Enums/UserRole.cs index 0bde3b08..59f7feff 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Users/Enums/UserRole.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Users/Enums/UserRole.cs @@ -2,21 +2,23 @@ namespace BotSharp.Abstraction.Users.Enums; public class UserRole { + public const string Root = "root"; + /// /// Admin account /// public const string Admin = "admin"; - /// - /// Customer service representative (CSR) - /// - public const string CSR = "csr"; - /// /// Authorized user /// public const string User = "user"; + /// + /// Customer service representative (CSR) + /// + public const string CSR = "csr"; + /// /// Back office operations /// @@ -33,6 +35,4 @@ public class UserRole /// AI Assistant /// public const string Assistant = "assistant"; - - public const string Root = "root"; } \ No newline at end of file diff --git a/src/Infrastructure/BotSharp.Abstraction/Users/IUserService.cs b/src/Infrastructure/BotSharp.Abstraction/Users/IUserService.cs index 77e40418..ea20307a 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Users/IUserService.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Users/IUserService.cs @@ -9,6 +9,8 @@ public interface IUserService Task GetUser(string id); Task> GetUsers(UserFilter filter); Task GetUserDetails(string userId); + Task IsAuthorizedUser(string userId); + Task GetUserAuthorizations(string? agentId = null); Task UpdateUser(User user, bool isUpdateUserAgents = false); Task CreateUser(User user); Task ActiveUser(UserActivationModel model); diff --git a/src/Infrastructure/BotSharp.Abstraction/Users/Models/UserAuthorization.cs b/src/Infrastructure/BotSharp.Abstraction/Users/Models/UserAuthorization.cs new file mode 100644 index 00000000..bf4ff025 --- /dev/null +++ b/src/Infrastructure/BotSharp.Abstraction/Users/Models/UserAuthorization.cs @@ -0,0 +1,8 @@ +namespace BotSharp.Abstraction.Users.Models; + +public class UserAuthorization +{ + public bool IsAdmin { get; set; } + public IEnumerable Permissions { get; set; } = []; + public IEnumerable AgentActions { get; set; } = []; +} diff --git a/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.CreateAgent.cs b/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.CreateAgent.cs index df10ac4b..7d4af3ac 100644 --- a/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.CreateAgent.cs +++ b/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.CreateAgent.cs @@ -25,8 +25,11 @@ public partial class AgentService var agentSettings = _services.GetRequiredService(); var user = _db.GetUserById(_user.Id); + var userService = _services.GetRequiredService(); + var auth = await userService.GetUserAuthorizations(); + _db.BulkInsertAgents(new List { agentRecord }); - if (!UserConstant.AdminRoles.Contains(user.Role)) + if (auth.IsAdmin || auth.Permissions.Contains(UserPermission.CreateAgent)) { _db.BulkInsertUserAgents(new List { @@ -34,7 +37,7 @@ public partial class AgentService { UserId = user.Id, AgentId = agentRecord.Id, - Actions = new List { UserAction.Edit, UserAction.Chat }, + Actions = new List { UserAction.Edit, UserAction.Train, UserAction.Evaluate, UserAction.Chat }, CreatedTime = DateTime.UtcNow, UpdatedTime = DateTime.UtcNow } diff --git a/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.DeleteAgent.cs b/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.DeleteAgent.cs index 8a05542e..e8cd7573 100644 --- a/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.DeleteAgent.cs +++ b/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.DeleteAgent.cs @@ -6,11 +6,10 @@ public partial class AgentService { public async Task DeleteAgent(string id) { - var user = _db.GetUserById(_user.Id); - var userAgents = await GetUserAgents(user?.Id); - var found = userAgents?.FirstOrDefault(x => x.AgentId == id); + var userService = _services.GetRequiredService(); + var auth = await userService.GetUserAuthorizations(id); - if (!UserConstant.AdminRoles.Contains(user?.Role) && (found?.Actions == null || !found.Actions.Contains(UserAction.Edit))) + if (auth.IsAdmin || auth.AgentActions.Contains(UserAction.Edit)) { return false; } diff --git a/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.RefreshAgents.cs b/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.RefreshAgents.cs index 61861aca..95268a17 100644 --- a/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.RefreshAgents.cs +++ b/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.RefreshAgents.cs @@ -17,8 +17,9 @@ public partial class AgentService return refreshResult; } - var user = _db.GetUserById(_user.Id); - if (!UserConstant.AdminRoles.Contains(user.Role)) + var userService = _services.GetRequiredService(); + var isValid = await userService.IsAuthorizedUser(_user.Id); + if (!isValid) { return "Unauthorized user."; } diff --git a/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.UpdateAgent.cs b/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.UpdateAgent.cs index 17aa4aa7..c6b2a743 100644 --- a/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.UpdateAgent.cs +++ b/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.UpdateAgent.cs @@ -12,12 +12,9 @@ public partial class AgentService if (agent == null || string.IsNullOrEmpty(agent.Id)) return; var userService = _services.GetRequiredService(); - var user = await userService.GetUser(_user.Id); + var auth = await userService.GetUserAuthorizations(agent.Id); - var userAgents = await GetUserAgents(user.Id); - var found = userAgents?.FirstOrDefault(x => x.AgentId == agent.Id); - - if (!UserConstant.AdminRoles.Contains(user?.Role) && (found?.Actions == null || found.Actions.Contains(UserAction.Edit))) + if (!auth.IsAdmin && !auth.AgentActions.Contains(UserAction.Edit)) { return; } diff --git a/src/Infrastructure/BotSharp.Core/BotSharpCoreExtensions.cs b/src/Infrastructure/BotSharp.Core/BotSharpCoreExtensions.cs index 4bdfe0d7..69d5b029 100644 --- a/src/Infrastructure/BotSharp.Core/BotSharpCoreExtensions.cs +++ b/src/Infrastructure/BotSharp.Core/BotSharpCoreExtensions.cs @@ -9,6 +9,7 @@ using BotSharp.Abstraction.Users.Settings; using BotSharp.Abstraction.Interpreters.Settings; using BotSharp.Abstraction.Infrastructures; using BotSharp.Core.Processors; +using BotSharp.Core.Roles.Services; namespace BotSharp.Core; @@ -23,6 +24,7 @@ public static class BotSharpCoreExtensions services.AddSingleton(); services.AddScoped(); + services.AddScoped(); services.AddScoped(); services.AddScoped(); diff --git a/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Agent.cs b/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Agent.cs index dadc3532..346eebd8 100644 --- a/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Agent.cs +++ b/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Agent.cs @@ -528,7 +528,7 @@ namespace BotSharp.Core.Repository var agentDir = GetAgentDataDir(agentId); if (string.IsNullOrEmpty(agentDir)) return false; - // Delete agent user relationships + // Delete user agents var usersDir = Path.Combine(_dbSettings.FileRepository, USERS_FOLDER); if (Directory.Exists(usersDir)) { @@ -546,6 +546,24 @@ namespace BotSharp.Core.Repository } } + // Delete role agents + var rolesDir = Path.Combine(_dbSettings.FileRepository, ROLES_FOLDER); + if (Directory.Exists(rolesDir)) + { + foreach (var roleDir in Directory.GetDirectories(rolesDir)) + { + var roleAgentFile = Directory.GetFiles(roleDir).FirstOrDefault(x => Path.GetFileName(x) == ROLE_AGENT_FILE); + if (string.IsNullOrEmpty(roleAgentFile)) continue; + + var text = File.ReadAllText(roleAgentFile); + var roleAgents = JsonSerializer.Deserialize>(text, _options); + if (roleAgents.IsNullOrEmpty()) continue; + + roleAgents = roleAgents?.Where(x => x.AgentId != agentId)?.ToList() ?? []; + File.WriteAllText(roleAgentFile, JsonSerializer.Serialize(roleAgents, _options)); + } + } + // Delete agent folder Directory.Delete(agentDir, true); Reset(); @@ -561,6 +579,7 @@ namespace BotSharp.Core.Repository { _agents = []; _userAgents = []; + _roleAgents = []; } } } diff --git a/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Role.cs b/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Role.cs index 13036a91..74acc45d 100644 --- a/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Role.cs +++ b/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Role.cs @@ -1,10 +1,39 @@ -using BotSharp.Abstraction.Users.Models; using System.IO; namespace BotSharp.Core.Repository; public partial class FileRepository { + public bool RefreshRoles(IEnumerable roles) + { + if (roles.IsNullOrEmpty()) return false; + + var validRoles = roles.Where(x => !string.IsNullOrWhiteSpace(x.Id) + && !string.IsNullOrWhiteSpace(x.Name)).ToList(); + if (validRoles.IsNullOrEmpty()) return false; + + var baseDir = Path.Combine(_dbSettings.FileRepository, ROLES_FOLDER); + if (Directory.Exists(baseDir)) + { + Directory.Delete(baseDir, true); + } + + Directory.CreateDirectory(baseDir); + + foreach (var role in validRoles) + { + var dir = Path.Combine(baseDir, role.Id); + Directory.CreateDirectory(dir); + Thread.Sleep(50); + var roleFile = Path.Combine(dir, ROLE_FILE); + role.CreatedTime = DateTime.UtcNow; + role.UpdatedTime = DateTime.UtcNow; + File.WriteAllText(roleFile, JsonSerializer.Serialize(role, _options)); + } + + return true; + } + public IEnumerable GetRoles(RoleFilter filter) { var roles = Roles; @@ -22,7 +51,7 @@ public partial class FileRepository return roles.ToList(); } - public Role? GetRoleDetails(string roleId) + public Role? GetRoleDetails(string roleId, bool includeAgent = false) { if (string.IsNullOrWhiteSpace(roleId)) return null; @@ -31,8 +60,20 @@ public partial class FileRepository var agentActions = new List(); var roleAgents = RoleAgents?.Where(x => x.RoleId == roleId)?.ToList() ?? []; - var agentIds = roleAgents.Select(x => x.AgentId).Distinct().ToList(); + if (!includeAgent) + { + agentActions = roleAgents.Select(x => new RoleAgentAction + { + Id = x.Id, + AgentId = x.AgentId, + Actions = x.Actions + }).ToList(); + role.AgentActions = agentActions; + return role; + } + + var agentIds = roleAgents.Select(x => x.AgentId).Distinct().ToList(); if (!agentIds.IsNullOrEmpty()) { var agents = GetAgents(new AgentFilter { AgentIds = agentIds }); @@ -56,7 +97,7 @@ public partial class FileRepository return role; } - public bool UpdateRole(Role role, bool isUpdateRoleAgents = false) + public bool UpdateRole(Role role, bool updateRoleAgents = false) { if (string.IsNullOrEmpty(role?.Id) || string.IsNullOrEmpty(role?.Name)) { @@ -74,7 +115,7 @@ public partial class FileRepository role.UpdatedTime = DateTime.UtcNow; File.WriteAllText(roleFile, JsonSerializer.Serialize(role, _options)); - if (isUpdateRoleAgents) + if (updateRoleAgents) { var roleAgents = role.AgentActions?.Select(x => new RoleAgent { diff --git a/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.User.cs b/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.User.cs index c55f248c..9600bbc8 100644 --- a/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.User.cs +++ b/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.User.cs @@ -1,7 +1,5 @@ -using BotSharp.Abstraction.Agents.Models; using BotSharp.Abstraction.Users.Enums; using BotSharp.Abstraction.Users.Models; -using System; using System.IO; namespace BotSharp.Core.Repository; @@ -97,6 +95,10 @@ public partial class FileRepository { users = users.Where(x => filter.Roles.Contains(x.Role)); } + if (!filter.Types.IsNullOrEmpty()) + { + users = users.Where(x => filter.Types.Contains(x.Type)); + } if (!filter.Sources.IsNullOrEmpty()) { users = users.Where(x => filter.Sources.Contains(x.Source)); @@ -109,17 +111,29 @@ public partial class FileRepository }; } - public User? GetUserDetails(string userId) + public User? GetUserDetails(string userId, bool includeAgent = false) { if (string.IsNullOrWhiteSpace(userId)) return null; - var user = Users.FirstOrDefault(x => x.Id == userId); + var user = Users.FirstOrDefault(x => x.Id == userId || x.ExternalId == userId); if (user == null) return null; var agentActions = new List(); var userAgents = UserAgents?.Where(x => x.UserId == userId)?.ToList() ?? []; - var agentIds = userAgents.Select(x => x.AgentId)?.Distinct().ToList(); + if (!includeAgent) + { + agentActions = userAgents.Select(x => new UserAgentAction + { + Id = x.Id, + AgentId = x.AgentId, + Actions = x.Actions + }).ToList(); + user.AgentActions = agentActions; + return user; + } + + var agentIds = userAgents.Select(x => x.AgentId)?.Distinct().ToList(); if (!agentIds.IsNullOrEmpty()) { var agents = GetAgents(new AgentFilter { AgentIds = agentIds }); @@ -143,7 +157,7 @@ public partial class FileRepository return user; } - public bool UpdateUser(User user, bool isUpdateUserAgents = false) + public bool UpdateUser(User user, bool updateUserAgents = false) { if (string.IsNullOrEmpty(user?.Id)) return false; @@ -157,7 +171,7 @@ public partial class FileRepository user.UpdatedTime = DateTime.UtcNow; File.WriteAllText(userFile, JsonSerializer.Serialize(user, _options)); - if (isUpdateUserAgents) + if (updateUserAgents) { var userAgents = user.AgentActions?.Select(x => new UserAgent { diff --git a/src/Infrastructure/BotSharp.Core/Roles/Services/RoleService.cs b/src/Infrastructure/BotSharp.Core/Roles/Services/RoleService.cs index b9f04cfe..4319d06e 100644 --- a/src/Infrastructure/BotSharp.Core/Roles/Services/RoleService.cs +++ b/src/Infrastructure/BotSharp.Core/Roles/Services/RoleService.cs @@ -16,6 +16,15 @@ public class RoleService : IRoleService _logger = logger; } + public async Task RefreshRoles() + { + var allRoles = await GetRoleOptions(); + var roles = allRoles.Select(x => new Role { Id = Guid.NewGuid().ToString(), Name = x }).ToList(); + + var db = _services.GetRequiredService(); + return db.RefreshRoles(roles); + } + public async Task> GetRoleOptions() { var fields = typeof(UserRole).GetFields(BindingFlags.Public | BindingFlags.Static) @@ -37,7 +46,7 @@ public class RoleService : IRoleService public async Task GetRoleDetails(string roleId) { var db = _services.GetRequiredService(); - var role = db.GetRoleDetails(roleId); + var role = db.GetRoleDetails(roleId, includeAgent: true); return role; } diff --git a/src/Infrastructure/BotSharp.Core/Users/Services/UserService.cs b/src/Infrastructure/BotSharp.Core/Users/Services/UserService.cs index 9fa2b73a..94cf845c 100644 --- a/src/Infrastructure/BotSharp.Core/Users/Services/UserService.cs +++ b/src/Infrastructure/BotSharp.Core/Users/Services/UserService.cs @@ -407,10 +407,54 @@ public class UserService : IUserService return users; } + public async Task IsAuthorizedUser(string userId) + { + var db = _services.GetRequiredService(); + var user = db.GetUserById(userId); + return user != null && UserConstant.AdminRoles.Contains(user.Role); + } + + public async Task GetUserAuthorizations(string? agentId = null) + { + var db = _services.GetRequiredService(); + var user = db.GetUserById(_user.Id); + var auth = new UserAuthorization(); + + if (user == null) return auth; + + var permissions = user.Permissions; + + var role = db.GetRoles(new RoleFilter { Names = [ user.Role ] }).FirstOrDefault(); + if (role != null && !permissions.Any()) + { + permissions = role.Permissions ?? []; + } + + auth.IsAdmin = UserConstant.AdminRoles.Contains(user.Role); + auth.Permissions = permissions; + + if (string.IsNullOrEmpty(agentId)) + { + return auth; + } + + var userAgent = db.GetUserDetails(user.Id)?.AgentActions?.FirstOrDefault(x => x.AgentId == agentId); + var actions = userAgent?.Actions ?? []; + + if (role != null && !actions.Any()) + { + var roleAgent = db.GetRoleDetails(role.Id)?.AgentActions?.FirstOrDefault(x => x.AgentId == agentId); + actions = roleAgent?.Actions ?? []; + } + + auth.AgentActions = actions; + return auth; + } + public async Task GetUserDetails(string userId) { var db = _services.GetRequiredService(); - return db.GetUserDetails(userId); + return db.GetUserDetails(userId, includeAgent: true); } public async Task UpdateUser(User user, bool isUpdateUserAgents = false) diff --git a/src/Infrastructure/BotSharp.OpenAPI/Controllers/AgentController.cs b/src/Infrastructure/BotSharp.OpenAPI/Controllers/AgentController.cs index 54c9b75e..06cfc77e 100644 --- a/src/Infrastructure/BotSharp.OpenAPI/Controllers/AgentController.cs +++ b/src/Infrastructure/BotSharp.OpenAPI/Controllers/AgentController.cs @@ -58,20 +58,11 @@ public class AgentController : ControllerBase rule.RedirectToAgentName = found.Name; } - var editable = true; - var chatable = true; var userService = _services.GetRequiredService(); - var user = await userService.GetUser(_user.Id); - if (!UserConstant.AdminRoles.Contains(user?.Role)) - { - var userAgents = await _agentService.GetUserAgents(user?.Id); - var actions = userAgents?.FirstOrDefault(x => x.AgentId == targetAgent.Id)?.Actions ?? []; - editable = actions.Contains(UserAction.Edit); - chatable = actions.Contains(UserAction.Chat); - } + var auth = await userService.GetUserAuthorizations(targetAgent.Id); - targetAgent.Editable = editable; - targetAgent.Chatable = chatable; + targetAgent.Editable = auth.IsAdmin || auth.AgentActions.Contains(UserAction.Edit); + targetAgent.Chatable = auth.IsAdmin || auth.AgentActions.Contains(UserAction.Chat); return targetAgent; } diff --git a/src/Infrastructure/BotSharp.OpenAPI/Controllers/PluginController.cs b/src/Infrastructure/BotSharp.OpenAPI/Controllers/PluginController.cs index e4ec4aa8..b6178f54 100644 --- a/src/Infrastructure/BotSharp.OpenAPI/Controllers/PluginController.cs +++ b/src/Infrastructure/BotSharp.OpenAPI/Controllers/PluginController.cs @@ -22,9 +22,8 @@ public class PluginController : ControllerBase [HttpGet("/plugins")] public async Task> GetPlugins([FromQuery] PluginFilter filter) { - var userService = _services.GetRequiredService(); - var user = await userService.GetUser(_user.Id); - if (!UserConstant.AdminRoles.Contains(user?.Role)) + var isValid = await IsValidUser(); + if (!isValid) { return new PagedItems(); } @@ -55,7 +54,11 @@ public class PluginController : ControllerBase { Roles = new List { UserRole.Root, UserRole.Admin } }, - new PluginMenuDef("Users", link: "page/users", icon: "bx bx-user", weight: 33) + new PluginMenuDef("Roles", link: "page/roles", icon: "bx bx-group", weight: 33) + { + Roles = new List { UserRole.Root, UserRole.Admin } + }, + new PluginMenuDef("Users", link: "page/users", icon: "bx bx-user", weight: 34) { Roles = new List { UserRole.Root, UserRole.Admin } } @@ -91,4 +94,10 @@ public class PluginController : ControllerBase var loader = _services.GetRequiredService(); return loader.UpdatePluginStatus(_services, id, false); } + + private async Task IsValidUser() + { + var userService = _services.GetRequiredService(); + return await userService.IsAuthorizedUser(_user.Id); + } } diff --git a/src/Infrastructure/BotSharp.OpenAPI/Controllers/RoleController.cs b/src/Infrastructure/BotSharp.OpenAPI/Controllers/RoleController.cs index 5eedc928..0bff6917 100644 --- a/src/Infrastructure/BotSharp.OpenAPI/Controllers/RoleController.cs +++ b/src/Infrastructure/BotSharp.OpenAPI/Controllers/RoleController.cs @@ -21,6 +21,19 @@ public class RoleController : ControllerBase _user = user; } + [HttpPost("/role/refresh")] + public async Task RefreshRoles() + { + var isValid = await IsValidUser(); + if (!isValid) + { + return false; + } + + return await _roleService.RefreshRoles(); + } + + [HttpGet("/role/options")] public async Task> GetRoleOptions() { @@ -35,6 +48,12 @@ public class RoleController : ControllerBase filter = RoleFilter.Empty(); } + var isValid = await IsValidUser(); + if (!isValid) + { + return Enumerable.Empty(); + } + var roles = await _roleService.GetRoles(filter); return roles.Select(x => RoleViewModel.FromRole(x)).ToList(); } @@ -51,9 +70,8 @@ public class RoleController : ControllerBase { if (model == null) return false; - var userService = _services.GetRequiredService(); - var user = await userService.GetUser(_user.Id); - if (user == null || !UserConstant.AdminRoles.Contains(user.Role)) + var isValid = await IsValidUser(); + if (!isValid) { return false; } @@ -61,4 +79,10 @@ public class RoleController : ControllerBase var role = RoleUpdateModel.ToRole(model); return await _roleService.UpdateRole(role, isUpdateRoleAgents: true); } + + private async Task IsValidUser() + { + var userService = _services.GetRequiredService(); + return await userService.IsAuthorizedUser(_user.Id); + } } diff --git a/src/Infrastructure/BotSharp.OpenAPI/Controllers/UserController.cs b/src/Infrastructure/BotSharp.OpenAPI/Controllers/UserController.cs index 366bb91b..5ea5f18c 100644 --- a/src/Infrastructure/BotSharp.OpenAPI/Controllers/UserController.cs +++ b/src/Infrastructure/BotSharp.OpenAPI/Controllers/UserController.cs @@ -182,8 +182,8 @@ public class UserController : ControllerBase public async Task> GetUsers([FromBody] UserFilter filter) { var userService = _services.GetRequiredService(); - var user = await userService.GetUser(_user.Id); - if (user == null || !UserConstant.AdminRoles.Contains(user.Role)) + var isValid = await IsValidUser(); + if (!isValid) { return new PagedItems(); } @@ -211,13 +211,13 @@ public class UserController : ControllerBase { if (model == null) return false; - var userService = _services.GetRequiredService(); - var user = await userService.GetUser(_user.Id); - if (user == null || !UserConstant.AdminRoles.Contains(user.Role)) + var isValid = await IsValidUser(); + if (!isValid) { return false; } + var userService = _services.GetRequiredService(); var updated = await userService.UpdateUser(UserUpdateModel.ToUser(model), isUpdateUserAgents: true); return updated; } @@ -252,6 +252,12 @@ public class UserController : ControllerBase #region Private methods + private async Task IsValidUser() + { + var userService = _services.GetRequiredService(); + return await userService.IsAuthorizedUser(_user.Id); + } + private FileContentResult BuildFileResult(string file) { var fileStorage = _services.GetRequiredService(); diff --git a/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Roles/RoleViewModel.cs b/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Roles/RoleViewModel.cs index e060b482..d4e8de33 100644 --- a/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Roles/RoleViewModel.cs +++ b/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Roles/RoleViewModel.cs @@ -18,10 +18,10 @@ public class RoleViewModel public IEnumerable AgentActions { get; set; } = []; [JsonPropertyName("create_date")] - public DateTime CreateDate { get; set; } + public DateTime? CreateDate { get; set; } [JsonPropertyName("update_date")] - public DateTime UpdateDate { get; set; } + public DateTime? UpdateDate { get; set; } public static RoleViewModel FromRole(Role? role) { @@ -33,8 +33,8 @@ public class RoleViewModel Name = role.Name, Permissions = role.Permissions, AgentActions = role.AgentActions?.Select(x => RoleAgentActionViewModel.ToViewModel(x)) ?? [], - CreateDate = role.CreatedTime, - UpdateDate = role.UpdatedTime + CreateDate = role.CreatedTime != default ? role.CreatedTime : null, + UpdateDate = role.UpdatedTime != default ? role.UpdatedTime : null }; } } diff --git a/src/Plugins/BotSharp.Plugin.MongoStorage/Collections/RoleAgentDocument.cs b/src/Plugins/BotSharp.Plugin.MongoStorage/Collections/RoleAgentDocument.cs new file mode 100644 index 00000000..037158d0 --- /dev/null +++ b/src/Plugins/BotSharp.Plugin.MongoStorage/Collections/RoleAgentDocument.cs @@ -0,0 +1,25 @@ +using BotSharp.Abstraction.Roles.Models; + +namespace BotSharp.Plugin.MongoStorage.Collections; + +public class RoleAgentDocument : MongoBase +{ + public string RoleId { get; set; } + public string AgentId { get; set; } + public IEnumerable Actions { get; set; } = []; + public DateTime CreatedTime { get; set; } + public DateTime UpdatedTime { get; set; } + + public RoleAgent ToRoleAgent() + { + return new RoleAgent + { + Id = Id, + RoleId = RoleId, + AgentId = AgentId, + Actions = Actions, + CreatedTime = CreatedTime, + UpdatedTime = UpdatedTime + }; + } +} diff --git a/src/Plugins/BotSharp.Plugin.MongoStorage/Collections/RoleDocument.cs b/src/Plugins/BotSharp.Plugin.MongoStorage/Collections/RoleDocument.cs new file mode 100644 index 00000000..557f219a --- /dev/null +++ b/src/Plugins/BotSharp.Plugin.MongoStorage/Collections/RoleDocument.cs @@ -0,0 +1,24 @@ +using BotSharp.Abstraction.Roles.Models; + +namespace BotSharp.Plugin.MongoStorage.Collections; + +public class RoleDocument : MongoBase +{ + public string Name { get; set; } + public IEnumerable Permissions { get; set; } = []; + public DateTime CreatedTime { get; set; } + public DateTime UpdatedTime { get; set; } + + + public Role ToRole() + { + return new Role + { + Id = Id, + Name = Name, + Permissions = Permissions, + CreatedTime = CreatedTime, + UpdatedTime = UpdatedTime + }; + } +} diff --git a/src/Plugins/BotSharp.Plugin.MongoStorage/MongoDbContext.cs b/src/Plugins/BotSharp.Plugin.MongoStorage/MongoDbContext.cs index af7c8b8f..b91c35fe 100644 --- a/src/Plugins/BotSharp.Plugin.MongoStorage/MongoDbContext.cs +++ b/src/Plugins/BotSharp.Plugin.MongoStorage/MongoDbContext.cs @@ -159,4 +159,10 @@ public class MongoDbContext public IMongoCollection KnowledgeCollectionFileMeta => Database.GetCollection($"{_collectionPrefix}_KnowledgeCollectionFileMeta"); + + public IMongoCollection Roles + => Database.GetCollection($"{_collectionPrefix}_Roles"); + + public IMongoCollection RoleAgents + => Database.GetCollection($"{_collectionPrefix}_RoleAgents"); } diff --git a/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.Agent.cs b/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.Agent.cs index b0146903..3e6dc000 100644 --- a/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.Agent.cs +++ b/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.Agent.cs @@ -2,6 +2,7 @@ using BotSharp.Abstraction.Agents.Models; using BotSharp.Abstraction.Functions.Models; using BotSharp.Abstraction.Repositories.Filters; using BotSharp.Abstraction.Routing.Models; +using MongoDB.Driver; namespace BotSharp.Plugin.MongoStorage.Repository; @@ -332,8 +333,6 @@ public partial class MongoRepository if (found.IsNullOrEmpty()) return []; - var agentIds = found.Select(x => x.AgentId).Distinct().ToList(); - var agents = GetAgents(new AgentFilter { AgentIds = agentIds }); var res = found.Select(x => new UserAgent { Id = x.Id, @@ -344,6 +343,8 @@ public partial class MongoRepository UpdatedTime = x.UpdatedTime }).ToList(); + var agentIds = found.Select(x => x.AgentId).Distinct().ToList(); + var agents = GetAgents(new AgentFilter { AgentIds = agentIds }); foreach (var item in res) { var agent = agents.FirstOrDefault(x => x.Id == item.AgentId); @@ -455,6 +456,7 @@ public partial class MongoRepository try { _dc.UserAgents.DeleteMany(Builders.Filter.Empty); + _dc.RoleAgents.DeleteMany(Builders.Filter.Empty); _dc.Agents.DeleteMany(Builders.Filter.Empty); return true; } @@ -472,10 +474,12 @@ public partial class MongoRepository var agentFilter = Builders.Filter.Eq(x => x.Id, agentId); var userAgentFilter = Builders.Filter.Eq(x => x.AgentId, agentId); + var roleAgentFilter = Builders.Filter.Eq(x => x.AgentId, agentId); var agentTaskFilter = Builders.Filter.Eq(x => x.AgentId, agentId); _dc.Agents.DeleteOne(agentFilter); _dc.UserAgents.DeleteMany(userAgentFilter); + _dc.RoleAgents.DeleteMany(roleAgentFilter); _dc.AgentTasks.DeleteMany(agentTaskFilter); return true; } diff --git a/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.Role.cs b/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.Role.cs new file mode 100644 index 00000000..ad3cf8db --- /dev/null +++ b/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.Role.cs @@ -0,0 +1,152 @@ +using BotSharp.Abstraction.Repositories.Filters; +using BotSharp.Abstraction.Roles.Models; + +namespace BotSharp.Plugin.MongoStorage.Repository; + +public partial class MongoRepository +{ + public bool RefreshRoles(IEnumerable roles) + { + if (roles.IsNullOrEmpty()) return false; + + var validRoles = roles.Where(x => !string.IsNullOrWhiteSpace(x.Id) + && !string.IsNullOrWhiteSpace(x.Name)).ToList(); + if (validRoles.IsNullOrEmpty()) return false; + + + // Clear data + _dc.RoleAgents.DeleteMany(Builders.Filter.Empty); + _dc.Roles.DeleteMany(Builders.Filter.Empty); + + var roleDocs = validRoles.Select(x => new RoleDocument + { + Id = x.Id, + Name = x.Name, + Permissions = x.Permissions, + CreatedTime = DateTime.UtcNow, + UpdatedTime = DateTime.UtcNow + }); + _dc.Roles.InsertMany(roleDocs); + + return true; + } + + + public IEnumerable GetRoles(RoleFilter filter) + { + if (filter == null) + { + filter = RoleFilter.Empty(); + } + + var roleBuilder = Builders.Filter; + var roleFilters = new List>() { roleBuilder.Empty }; + + // Apply filters + if (!filter.Names.IsNullOrEmpty()) + { + roleFilters.Add(roleBuilder.In(x => x.Name, filter.Names)); + } + + // Search + var roleDocs = _dc.Roles.Find(roleBuilder.And(roleFilters)).ToList(); + var roles = roleDocs.Select(x => x.ToRole()).ToList(); + + return roles; + } + + public Role? GetRoleDetails(string roleId, bool includeAgent = false) + { + if (string.IsNullOrWhiteSpace(roleId)) return null; + + var roleDoc = _dc.Roles.Find(Builders.Filter.Eq(x => x.Id, roleId)).FirstOrDefault(); + if (roleDoc == null) return null; + + var agentActions = new List(); + var role = roleDoc.ToRole(); + var roleAgentDocs = _dc.RoleAgents.Find(Builders.Filter.Eq(x => x.RoleId, roleId)).ToList(); + + if (!includeAgent) + { + agentActions = roleAgentDocs.Select(x => new RoleAgentAction + { + Id = x.Id, + AgentId = x.AgentId, + Actions = x.Actions + }).ToList(); + role.AgentActions = agentActions; + return role; + } + + var agentIds = roleAgentDocs.Select(x => x.AgentId).Distinct().ToList(); + if (!agentIds.IsNullOrEmpty()) + { + var agents = GetAgents(new AgentFilter { AgentIds = agentIds }); + + foreach (var item in roleAgentDocs) + { + var found = agents.FirstOrDefault(x => x.Id == item.AgentId); + if (found == null) continue; + + agentActions.Add(new RoleAgentAction + { + Id = item.Id, + AgentId = found.Id, + Agent = found, + Actions = item.Actions + }); + } + } + + role.AgentActions = agentActions; + return role; + } + + public bool UpdateRole(Role role, bool updateRoleAgents = false) + { + if (string.IsNullOrEmpty(role?.Id)) return false; + + var roleFilter = Builders.Filter.Eq(x => x.Id, role.Id); + var roleUpdate = Builders.Update + .Set(x => x.Name, role.Name) + .Set(x => x.Permissions, role.Permissions) + .Set(x => x.CreatedTime, DateTime.UtcNow) + .Set(x => x.UpdatedTime, DateTime.UtcNow); + + _dc.Roles.UpdateOne(roleFilter, roleUpdate, _options); + + if (updateRoleAgents) + { + var roleAgentDocs = role.AgentActions?.Select(x => new RoleAgentDocument + { + Id = !string.IsNullOrEmpty(x.Id) ? x.Id : Guid.NewGuid().ToString(), + RoleId = role.Id, + AgentId = x.AgentId, + Actions = x.Actions, + CreatedTime = DateTime.UtcNow, + UpdatedTime = DateTime.UtcNow + })?.ToList() ?? []; + + var toDelete = _dc.RoleAgents.Find(Builders.Filter.And( + Builders.Filter.Eq(x => x.RoleId, role.Id), + Builders.Filter.Nin(x => x.Id, roleAgentDocs.Select(x => x.Id)) + )).ToList(); + + _dc.RoleAgents.DeleteMany(Builders.Filter.In(x => x.Id, toDelete.Select(x => x.Id))); + foreach (var doc in roleAgentDocs) + { + var roleAgentFilter = Builders.Filter.Eq(x => x.Id, doc.Id); + var roleAgentUpdate = Builders.Update + .Set(x => x.Id, doc.Id) + .Set(x => x.RoleId, role.Id) + .Set(x => x.AgentId, doc.AgentId) + .Set(x => x.Actions, doc.Actions) + .Set(x => x.UpdatedTime, DateTime.UtcNow); + + _dc.RoleAgents.UpdateOne(roleAgentFilter, roleAgentUpdate, _options); + } + } + + return true; + } +} diff --git a/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.User.cs b/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.User.cs index d7115c36..374d2ab0 100644 --- a/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.User.cs +++ b/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.User.cs @@ -200,6 +200,10 @@ public partial class MongoRepository { userFilters.Add(userBuilder.In(x => x.Role, filter.Roles)); } + if (!filter.Types.IsNullOrEmpty()) + { + userFilters.Add(userBuilder.In(x => x.Type, filter.Types)); + } if (!filter.Sources.IsNullOrEmpty()) { userFilters.Add(userBuilder.In(x => x.Source, filter.Sources)); @@ -221,15 +225,15 @@ public partial class MongoRepository }; } - public User? GetUserDetails(string userId) + public User? GetUserDetails(string userId, bool includeAgent = false) { if (string.IsNullOrWhiteSpace(userId)) return null; - var userDoc = _dc.Users.Find(Builders.Filter.Eq(x => x.Id, userId)).FirstOrDefault(); + var userDoc = _dc.Users.AsQueryable().FirstOrDefault(x => x.Id == userId || x.ExternalId == userId); if (userDoc == null) return null; + var agentActions = new List(); var user = userDoc.ToUser(); - var userAgents = _dc.UserAgents.AsQueryable().Where(x => x.UserId == userId).Select(x => new UserAgent { Id = x.Id, @@ -238,9 +242,19 @@ public partial class MongoRepository Actions = x.Actions ?? Enumerable.Empty() }).ToList(); - var agentActions = new List(); + if (!includeAgent) + { + agentActions = userAgents.Select(x => new UserAgentAction + { + Id = x.Id, + AgentId = x.AgentId, + Actions = x.Actions + }).ToList(); + user.AgentActions = agentActions; + return user; + } + var agentIds = userAgents.Select(x => x.AgentId)?.Distinct().ToList(); - if (!agentIds.IsNullOrEmpty()) { var agents = GetAgents(new AgentFilter { AgentIds = agentIds }); @@ -264,7 +278,7 @@ public partial class MongoRepository return user; } - public bool UpdateUser(User user, bool isUpdateUserAgents = false) + public bool UpdateUser(User user, bool updateUserAgents = false) { if (string.IsNullOrEmpty(user?.Id)) return false; @@ -277,7 +291,7 @@ public partial class MongoRepository _dc.Users.UpdateOne(userFilter, userUpdate); - if (isUpdateUserAgents) + if (updateUserAgents) { var userAgentDocs = user.AgentActions?.Select(x => new UserAgentDocument { From 00b244227b591a7f47da0dcefe3ddadf99bb08b7 Mon Sep 17 00:00:00 2001 From: YouWeiDH Date: Fri, 15 Nov 2024 09:53:51 +0800 Subject: [PATCH 18/29] hdong: merge code. --- .../Repositories/IBotSharpRepository.cs | 3 +-- .../Users/Services/UserService.cs | 2 +- .../Repository/MongoRepository.User.cs | 19 +++---------------- 3 files changed, 5 insertions(+), 19 deletions(-) diff --git a/src/Infrastructure/BotSharp.Abstraction/Repositories/IBotSharpRepository.cs b/src/Infrastructure/BotSharp.Abstraction/Repositories/IBotSharpRepository.cs index 9282506d..fb3fb187 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Repositories/IBotSharpRepository.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Repositories/IBotSharpRepository.cs @@ -18,8 +18,7 @@ public interface IBotSharpRepository : IHaveServiceProvider #region User User? GetUserByEmail(string email) => throw new NotImplementedException(); - User? GetUserByPhone(string phone, string regionCode = "CN") => throw new NotImplementedException(); - User? GetAdminUserByPhone(string phone, string regionCode = "CN") => throw new NotImplementedException(); + User? GetUserByPhone(string phone, string role = null, string regionCode = "CN") => throw new NotImplementedException(); User? GetAffiliateUserByPhone(string phone) => throw new NotImplementedException(); User? GetUserById(string id) => throw new NotImplementedException(); List GetUserByIds(List ids) => throw new NotImplementedException(); diff --git a/src/Infrastructure/BotSharp.Core/Users/Services/UserService.cs b/src/Infrastructure/BotSharp.Core/Users/Services/UserService.cs index ba97bae8..d152cad4 100644 --- a/src/Infrastructure/BotSharp.Core/Users/Services/UserService.cs +++ b/src/Infrastructure/BotSharp.Core/Users/Services/UserService.cs @@ -172,7 +172,7 @@ public class UserService : IUserService var base64 = Encoding.UTF8.GetString(Convert.FromBase64String(authorization)); var (id, password) = base64.SplitAsTuple(":"); var db = _services.GetRequiredService(); - var record = db.GetAdminUserByPhone(id); + var record = db.GetUserByPhone(id,"admin"); var isCanLogin = record != null && !record.IsDisabled && record.Type == UserType.Internal && new List { diff --git a/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.User.cs b/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.User.cs index bfaecbf5..01ed871e 100644 --- a/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.User.cs +++ b/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.User.cs @@ -13,7 +13,7 @@ public partial class MongoRepository return user != null ? user.ToUser() : null; } - public User? GetUserByPhone(string phone, string regionCode = "CN") + public User? GetUserByPhone(string phone, string role = null, string regionCode = "CN") { if (string.IsNullOrWhiteSpace(phone)) { @@ -23,21 +23,8 @@ public partial class MongoRepository string phoneSecond = phone.StartsWith("+86") ? phone.Replace("+86", "") : $"+86{phone}"; var user = _dc.Users.AsQueryable().FirstOrDefault(x => (x.Phone == phone || x.Phone == phoneSecond) && x.Type != UserType.Affiliate - && (x.RegionCode == regionCode || string.IsNullOrWhiteSpace(x.RegionCode))); - return user != null ? user.ToUser() : null; - } - - public User? GetAdminUserByPhone(string phone, string regionCode = "CN") - { - if (string.IsNullOrWhiteSpace(phone)) - { - return null; - } - - string phoneSecond = phone.StartsWith("+86") ? phone.Replace("+86", "") : $"+86{phone}"; - - var user = _dc.Users.AsQueryable().FirstOrDefault(x => (x.Phone == phone || x.Phone == phoneSecond) && x.Type != UserType.Affiliate - && (x.RegionCode == regionCode || string.IsNullOrWhiteSpace(x.RegionCode)) && (x.Role == "admin" || x.Role == "root")); + && (x.RegionCode == regionCode || string.IsNullOrWhiteSpace(x.RegionCode)) + && (role == "admin" ? x.Role == "admin" || x.Role == "root" : true)); return user != null ? user.ToUser() : null; } From 39862dfab6f861de7938a48394ae9d464ec1e6bf Mon Sep 17 00:00:00 2001 From: Jicheng Lu <103353@smsassist.com> Date: Thu, 14 Nov 2024 20:19:53 -0600 Subject: [PATCH 19/29] refine role auth --- .../Users/IUserService.cs | 2 +- .../Users/Models/UserAuthorization.cs | 19 +++++++++- .../Services/AgentService.DeleteAgent.cs | 5 ++- .../Services/AgentService.UpdateAgent.cs | 6 ++- .../Users/Services/UserService.cs | 37 ++++++++++--------- .../Controllers/AgentController.cs | 32 +++++----------- .../ViewModels/Agents/AgentViewModel.cs | 2 + .../Repository/MongoRepository.User.cs | 9 ++--- 8 files changed, 60 insertions(+), 52 deletions(-) diff --git a/src/Infrastructure/BotSharp.Abstraction/Users/IUserService.cs b/src/Infrastructure/BotSharp.Abstraction/Users/IUserService.cs index ea20307a..abeb06d9 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Users/IUserService.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Users/IUserService.cs @@ -10,7 +10,7 @@ public interface IUserService Task> GetUsers(UserFilter filter); Task GetUserDetails(string userId); Task IsAuthorizedUser(string userId); - Task GetUserAuthorizations(string? agentId = null); + Task GetUserAuthorizations(IEnumerable? agentIds = null); Task UpdateUser(User user, bool isUpdateUserAgents = false); Task CreateUser(User user); Task ActiveUser(UserActivationModel model); diff --git a/src/Infrastructure/BotSharp.Abstraction/Users/Models/UserAuthorization.cs b/src/Infrastructure/BotSharp.Abstraction/Users/Models/UserAuthorization.cs index bf4ff025..56df6a7d 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Users/Models/UserAuthorization.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Users/Models/UserAuthorization.cs @@ -4,5 +4,22 @@ public class UserAuthorization { public bool IsAdmin { get; set; } public IEnumerable Permissions { get; set; } = []; - public IEnumerable AgentActions { get; set; } = []; + public IEnumerable AgentActions { get; set; } = []; } + + +public static class UserAuthorizationExtension +{ + public static bool IsAgentActionAllowed(this UserAuthorization auth, string agentId, string targetAction) + { + if (auth == null || string.IsNullOrEmpty(agentId)) return false; + + if (auth.IsAdmin) return true; + + var found = auth.AgentActions.FirstOrDefault(x => x.AgentId == agentId); + if (found == null) return false; + + var actions = found.Actions ?? []; + return actions.Any(x => x == targetAction); + } +} \ No newline at end of file diff --git a/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.DeleteAgent.cs b/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.DeleteAgent.cs index e8cd7573..6783bf91 100644 --- a/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.DeleteAgent.cs +++ b/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.DeleteAgent.cs @@ -1,4 +1,5 @@ using BotSharp.Abstraction.Users.Enums; +using BotSharp.Abstraction.Users.Models; namespace BotSharp.Core.Agents.Services; @@ -7,9 +8,9 @@ public partial class AgentService public async Task DeleteAgent(string id) { var userService = _services.GetRequiredService(); - var auth = await userService.GetUserAuthorizations(id); + var auth = await userService.GetUserAuthorizations(new List { id }); - if (auth.IsAdmin || auth.AgentActions.Contains(UserAction.Edit)) + if (!auth.IsAgentActionAllowed(id, UserAction.Edit)) { return false; } diff --git a/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.UpdateAgent.cs b/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.UpdateAgent.cs index c6b2a743..18afb27f 100644 --- a/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.UpdateAgent.cs +++ b/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.UpdateAgent.cs @@ -1,6 +1,7 @@ using BotSharp.Abstraction.Repositories.Enums; using BotSharp.Abstraction.Routing.Models; using BotSharp.Abstraction.Users.Enums; +using BotSharp.Abstraction.Users.Models; using System.IO; namespace BotSharp.Core.Agents.Services; @@ -12,9 +13,10 @@ public partial class AgentService if (agent == null || string.IsNullOrEmpty(agent.Id)) return; var userService = _services.GetRequiredService(); - var auth = await userService.GetUserAuthorizations(agent.Id); + var auth = await userService.GetUserAuthorizations(new List { agent.Id }); + var allowEdit = auth.IsAgentActionAllowed(agent.Id, UserAction.Edit); - if (!auth.IsAdmin && !auth.AgentActions.Contains(UserAction.Edit)) + if (!allowEdit) { return; } diff --git a/src/Infrastructure/BotSharp.Core/Users/Services/UserService.cs b/src/Infrastructure/BotSharp.Core/Users/Services/UserService.cs index 94cf845c..e07d1be4 100644 --- a/src/Infrastructure/BotSharp.Core/Users/Services/UserService.cs +++ b/src/Infrastructure/BotSharp.Core/Users/Services/UserService.cs @@ -414,7 +414,7 @@ public class UserService : IUserService return user != null && UserConstant.AdminRoles.Contains(user.Role); } - public async Task GetUserAuthorizations(string? agentId = null) + public async Task GetUserAuthorizations(IEnumerable? agentIds = null) { var db = _services.GetRequiredService(); var user = db.GetUserById(_user.Id); @@ -422,32 +422,33 @@ public class UserService : IUserService if (user == null) return auth; - var permissions = user.Permissions; - - var role = db.GetRoles(new RoleFilter { Names = [ user.Role ] }).FirstOrDefault(); - if (role != null && !permissions.Any()) - { - permissions = role.Permissions ?? []; - } - auth.IsAdmin = UserConstant.AdminRoles.Contains(user.Role); + + var role = db.GetRoles(new RoleFilter { Names = [user.Role] }).FirstOrDefault(); + var permissions = user.Permissions?.Any() == true ? user.Permissions : role?.Permissions ?? []; auth.Permissions = permissions; - if (string.IsNullOrEmpty(agentId)) + if (agentIds == null || !agentIds.Any()) { return auth; } - var userAgent = db.GetUserDetails(user.Id)?.AgentActions?.FirstOrDefault(x => x.AgentId == agentId); - var actions = userAgent?.Actions ?? []; + var userAgents = db.GetUserDetails(user.Id)?.AgentActions? + .Where(x => agentIds.Contains(x.AgentId) && x.Actions.Any())?.Select(x => new UserAgent + { + AgentId = x.AgentId, + Actions = x.Actions + }).ToList() ?? []; - if (role != null && !actions.Any()) - { - var roleAgent = db.GetRoleDetails(role.Id)?.AgentActions?.FirstOrDefault(x => x.AgentId == agentId); - actions = roleAgent?.Actions ?? []; - } + var userAgentIds = userAgents.Select(x => x.AgentId).ToList(); + var roleAgents = db.GetRoleDetails(role?.Id)?.AgentActions? + .Where(x => !userAgentIds.Contains(x.AgentId))?.Select(x => new UserAgent + { + AgentId = x.AgentId, + Actions = x.Actions + })?.ToList() ?? []; - auth.AgentActions = actions; + auth.AgentActions = userAgents.Concat(roleAgents); return auth; } diff --git a/src/Infrastructure/BotSharp.OpenAPI/Controllers/AgentController.cs b/src/Infrastructure/BotSharp.OpenAPI/Controllers/AgentController.cs index 06cfc77e..5ce68c2a 100644 --- a/src/Infrastructure/BotSharp.OpenAPI/Controllers/AgentController.cs +++ b/src/Infrastructure/BotSharp.OpenAPI/Controllers/AgentController.cs @@ -1,4 +1,3 @@ -using BotSharp.Abstraction.Agents.Models; using BotSharp.Abstraction.Users.Enums; namespace BotSharp.OpenAPI.Controllers; @@ -59,10 +58,12 @@ public class AgentController : ControllerBase } var userService = _services.GetRequiredService(); - var auth = await userService.GetUserAuthorizations(targetAgent.Id); + var auth = await userService.GetUserAuthorizations(new List { targetAgent.Id }); - targetAgent.Editable = auth.IsAdmin || auth.AgentActions.Contains(UserAction.Edit); - targetAgent.Chatable = auth.IsAdmin || auth.AgentActions.Contains(UserAction.Chat); + targetAgent.Editable = auth.IsAgentActionAllowed(targetAgent.Id, UserAction.Edit); + targetAgent.Chatable = auth.IsAgentActionAllowed(targetAgent.Id, UserAction.Chat); + targetAgent.Trainable = auth.IsAgentActionAllowed(targetAgent.Id, UserAction.Train); + targetAgent.Evaluable = auth.IsAgentActionAllowed(targetAgent.Id, UserAction.Evaluate); return targetAgent; } @@ -85,27 +86,14 @@ public class AgentController : ControllerBase }; } - var userAgents = new List(); - var user = await userService.GetUser(_user.Id); - if (!UserConstant.AdminRoles.Contains(user.Role)) - { - userAgents = await _agentService.GetUserAgents(user.Id); - } - + var auth = await userService.GetUserAuthorizations(pagedAgents.Items.Select(x => x.Id)); agents = pagedAgents?.Items?.Select(x => { - var chatable = true; - var editable = true; - if (!UserConstant.AdminRoles.Contains(user.Role)) - { - var actions = userAgents.FirstOrDefault(a => a.AgentId == x.Id)?.Actions ?? []; - chatable = actions.Contains(UserAction.Chat); - editable = actions.Contains(UserAction.Edit); - } - var model = AgentViewModel.FromAgent(x); - model.Editable = editable; - model.Chatable = chatable; + model.Editable = auth.IsAgentActionAllowed(x.Id, UserAction.Edit); + model.Chatable = auth.IsAgentActionAllowed(x.Id, UserAction.Chat); + model.Trainable = auth.IsAgentActionAllowed(x.Id, UserAction.Train); + model.Evaluable = auth.IsAgentActionAllowed(x.Id, UserAction.Evaluate); return model; })?.ToList() ?? []; diff --git a/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Agents/AgentViewModel.cs b/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Agents/AgentViewModel.cs index b368cde0..ca814177 100644 --- a/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Agents/AgentViewModel.cs +++ b/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Agents/AgentViewModel.cs @@ -48,6 +48,8 @@ public class AgentViewModel public bool Editable { get; set; } public bool Chatable { get; set; } + public bool Trainable { get; set; } + public bool Evaluable { get; set; } [JsonPropertyName("created_datetime")] public DateTime CreatedDateTime { get; set; } diff --git a/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.User.cs b/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.User.cs index 374d2ab0..cb857aa0 100644 --- a/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.User.cs +++ b/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.User.cs @@ -43,22 +43,19 @@ public partial class MongoRepository public User? GetUserById(string id) { - var user = _dc.Users.AsQueryable() - .FirstOrDefault(x => x.Id == id || (x.ExternalId != null && x.ExternalId == id)); + var user = _dc.Users.AsQueryable().FirstOrDefault(x => x.Id == id || (x.ExternalId != null && x.ExternalId == id)); return user != null ? user.ToUser() : null; } public List GetUserByIds(List ids) { - var users = _dc.Users.AsQueryable() - .Where(x => ids.Contains(x.Id) || (x.ExternalId != null && ids.Contains(x.ExternalId))).ToList(); + var users = _dc.Users.AsQueryable().Where(x => ids.Contains(x.Id) || (x.ExternalId != null && ids.Contains(x.ExternalId))).ToList(); return users?.Any() == true ? users.Select(x => x.ToUser()).ToList() : new List(); } public List GetUsersByAffiliateId(string affiliateId) { - var users = _dc.Users.AsQueryable() - .Where(x => x.AffiliateId == affiliateId).ToList(); + var users = _dc.Users.AsQueryable().Where(x => x.AffiliateId == affiliateId).ToList(); return users?.Any() == true ? users.Select(x => x.ToUser()).ToList() : new List(); } From 6ef25b139c4f18872d7deb58a21ec8665c0ca889 Mon Sep 17 00:00:00 2001 From: Jicheng Lu <103353@smsassist.com> Date: Thu, 14 Nov 2024 20:23:45 -0600 Subject: [PATCH 20/29] change name --- src/Infrastructure/BotSharp.Abstraction/Users/IUserService.cs | 2 +- .../Agents/Services/AgentService.RefreshAgents.cs | 3 +-- src/Infrastructure/BotSharp.Core/Users/Services/UserService.cs | 2 +- .../BotSharp.OpenAPI/Controllers/PluginController.cs | 2 +- .../BotSharp.OpenAPI/Controllers/RoleController.cs | 2 +- .../BotSharp.OpenAPI/Controllers/UserController.cs | 2 +- 6 files changed, 6 insertions(+), 7 deletions(-) diff --git a/src/Infrastructure/BotSharp.Abstraction/Users/IUserService.cs b/src/Infrastructure/BotSharp.Abstraction/Users/IUserService.cs index abeb06d9..73605789 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Users/IUserService.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Users/IUserService.cs @@ -9,7 +9,7 @@ public interface IUserService Task GetUser(string id); Task> GetUsers(UserFilter filter); Task GetUserDetails(string userId); - Task IsAuthorizedUser(string userId); + Task IsAdminUser(string userId); Task GetUserAuthorizations(IEnumerable? agentIds = null); Task UpdateUser(User user, bool isUpdateUserAgents = false); Task CreateUser(User user); diff --git a/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.RefreshAgents.cs b/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.RefreshAgents.cs index 95268a17..f0838ce5 100644 --- a/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.RefreshAgents.cs +++ b/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.RefreshAgents.cs @@ -1,5 +1,4 @@ using BotSharp.Abstraction.Repositories.Enums; -using BotSharp.Abstraction.Users.Enums; using System.IO; namespace BotSharp.Core.Agents.Services; @@ -18,7 +17,7 @@ public partial class AgentService } var userService = _services.GetRequiredService(); - var isValid = await userService.IsAuthorizedUser(_user.Id); + var isValid = await userService.IsAdminUser(_user.Id); if (!isValid) { return "Unauthorized user."; diff --git a/src/Infrastructure/BotSharp.Core/Users/Services/UserService.cs b/src/Infrastructure/BotSharp.Core/Users/Services/UserService.cs index e07d1be4..5e5572ec 100644 --- a/src/Infrastructure/BotSharp.Core/Users/Services/UserService.cs +++ b/src/Infrastructure/BotSharp.Core/Users/Services/UserService.cs @@ -407,7 +407,7 @@ public class UserService : IUserService return users; } - public async Task IsAuthorizedUser(string userId) + public async Task IsAdminUser(string userId) { var db = _services.GetRequiredService(); var user = db.GetUserById(userId); diff --git a/src/Infrastructure/BotSharp.OpenAPI/Controllers/PluginController.cs b/src/Infrastructure/BotSharp.OpenAPI/Controllers/PluginController.cs index b6178f54..499ebdf7 100644 --- a/src/Infrastructure/BotSharp.OpenAPI/Controllers/PluginController.cs +++ b/src/Infrastructure/BotSharp.OpenAPI/Controllers/PluginController.cs @@ -98,6 +98,6 @@ public class PluginController : ControllerBase private async Task IsValidUser() { var userService = _services.GetRequiredService(); - return await userService.IsAuthorizedUser(_user.Id); + return await userService.IsAdminUser(_user.Id); } } diff --git a/src/Infrastructure/BotSharp.OpenAPI/Controllers/RoleController.cs b/src/Infrastructure/BotSharp.OpenAPI/Controllers/RoleController.cs index 0bff6917..d270ecbe 100644 --- a/src/Infrastructure/BotSharp.OpenAPI/Controllers/RoleController.cs +++ b/src/Infrastructure/BotSharp.OpenAPI/Controllers/RoleController.cs @@ -83,6 +83,6 @@ public class RoleController : ControllerBase private async Task IsValidUser() { var userService = _services.GetRequiredService(); - return await userService.IsAuthorizedUser(_user.Id); + return await userService.IsAdminUser(_user.Id); } } diff --git a/src/Infrastructure/BotSharp.OpenAPI/Controllers/UserController.cs b/src/Infrastructure/BotSharp.OpenAPI/Controllers/UserController.cs index 5ea5f18c..6bdbd721 100644 --- a/src/Infrastructure/BotSharp.OpenAPI/Controllers/UserController.cs +++ b/src/Infrastructure/BotSharp.OpenAPI/Controllers/UserController.cs @@ -255,7 +255,7 @@ public class UserController : ControllerBase private async Task IsValidUser() { var userService = _services.GetRequiredService(); - return await userService.IsAuthorizedUser(_user.Id); + return await userService.IsAdminUser(_user.Id); } private FileContentResult BuildFileResult(string file) From 7ff43b4fa3b8d496b939c09e8af15ce8b90e274f Mon Sep 17 00:00:00 2001 From: YouWeiDH Date: Fri, 15 Nov 2024 11:13:13 +0800 Subject: [PATCH 21/29] hdong:add parameter in cachekey. --- .../BotSharp.Abstraction/Utilities/Pagination.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Infrastructure/BotSharp.Abstraction/Utilities/Pagination.cs b/src/Infrastructure/BotSharp.Abstraction/Utilities/Pagination.cs index 8d6d0e0d..8e5c7f23 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Utilities/Pagination.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Utilities/Pagination.cs @@ -8,9 +8,9 @@ public class Pagination : ICacheKey private int _size; public int Page - { + { get { return _page > 0 ? _page : 1; } - set { _page = value; } + set { _page = value; } } public int Size @@ -43,7 +43,7 @@ public class Pagination : ICacheKey public bool ReturnTotal { get; set; } = true; public string GetCacheKey() - => $"{nameof(Pagination)}_{_page}_{_size}_{Sort}_{Order}"; + => $"{nameof(Pagination)}_{_page}_{_size}_{Sort}_{Order}_{Offset}_{ReturnTotal}"; } public class PagedItems From b548cfe436a67981fc15608a4c401521e529ce77 Mon Sep 17 00:00:00 2001 From: Jicheng Lu <103353@smsassist.com> Date: Fri, 15 Nov 2024 10:36:17 -0600 Subject: [PATCH 22/29] add similar search --- .../Knowledges/IKnowledgeService.cs | 2 +- .../Knowledges/Models/ChunkOption.cs | 11 +++++++++++ .../Plugins/Models/PluginFilter.cs | 1 + .../Repositories/Filters/AgentFilter.cs | 1 + .../BotSharpSideCarPlugin.cs | 2 +- .../BotSharp.Core/Plugins/PluginLoader.cs | 7 +++++++ .../FileRepository/FileRepository.Agent.cs | 18 ++++++++++++++---- .../FileRepository.Conversation.cs | 1 + .../Services/KnowledgeService.Document.cs | 18 +++++++----------- .../Repository/MongoRepository.Agent.cs | 5 +++++ .../Repository/MongoRepository.User.cs | 2 +- 11 files changed, 50 insertions(+), 18 deletions(-) diff --git a/src/Infrastructure/BotSharp.Abstraction/Knowledges/IKnowledgeService.cs b/src/Infrastructure/BotSharp.Abstraction/Knowledges/IKnowledgeService.cs index 2313b2ba..2bc70dc2 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Knowledges/IKnowledgeService.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Knowledges/IKnowledgeService.cs @@ -30,7 +30,7 @@ public interface IKnowledgeService /// /// /// - Task UploadDocumentsToKnowledge(string collectionName, IEnumerable files); + Task UploadDocumentsToKnowledge(string collectionName, IEnumerable files, ChunkOption? option = null); /// /// Save document content to knowledgebase without saving the document /// diff --git a/src/Infrastructure/BotSharp.Abstraction/Knowledges/Models/ChunkOption.cs b/src/Infrastructure/BotSharp.Abstraction/Knowledges/Models/ChunkOption.cs index 936a41fb..72c474bf 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Knowledges/Models/ChunkOption.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Knowledges/Models/ChunkOption.cs @@ -13,4 +13,15 @@ public class ChunkOption public int Conjunction { get; set; } public bool SplitByWord { get; set; } + + + public static ChunkOption Default() + { + return new ChunkOption + { + Size = 1024, + Conjunction = 12, + SplitByWord = true, + }; + } } diff --git a/src/Infrastructure/BotSharp.Abstraction/Plugins/Models/PluginFilter.cs b/src/Infrastructure/BotSharp.Abstraction/Plugins/Models/PluginFilter.cs index e7798d6c..0bd9a42d 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Plugins/Models/PluginFilter.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Plugins/Models/PluginFilter.cs @@ -4,5 +4,6 @@ namespace BotSharp.Abstraction.Plugins.Models { public Pagination Pager { get; set; } = new Pagination(); public IEnumerable? Names { get; set; } + public string? SimilarName { get; set; } } } diff --git a/src/Infrastructure/BotSharp.Abstraction/Repositories/Filters/AgentFilter.cs b/src/Infrastructure/BotSharp.Abstraction/Repositories/Filters/AgentFilter.cs index 7de61b76..e6060ae4 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Repositories/Filters/AgentFilter.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Repositories/Filters/AgentFilter.cs @@ -4,6 +4,7 @@ public class AgentFilter { public Pagination Pager { get; set; } = new Pagination(); public string? AgentName { get; set; } + public string? SimilarName { get; set; } public bool? Disabled { get; set; } public bool? Installed { get; set; } public string? Type { get; set; } diff --git a/src/Infrastructure/BotSharp.Core.SideCar/BotSharpSideCarPlugin.cs b/src/Infrastructure/BotSharp.Core.SideCar/BotSharpSideCarPlugin.cs index efacd308..efef5201 100644 --- a/src/Infrastructure/BotSharp.Core.SideCar/BotSharpSideCarPlugin.cs +++ b/src/Infrastructure/BotSharp.Core.SideCar/BotSharpSideCarPlugin.cs @@ -8,7 +8,7 @@ namespace BotSharp.Core.SideCar; public class BotSharpSideCarPlugin : IBotSharpPlugin { public string Id => "06e5a276-bba0-45af-9625-889267c341c9"; - public string Name => "Side car"; + public string Name => "Side Car"; public string Description => "Provides side car for calling agent cluster in conversation"; public SettingsMeta Settings => new SettingsMeta("SideCar"); diff --git a/src/Infrastructure/BotSharp.Core/Plugins/PluginLoader.cs b/src/Infrastructure/BotSharp.Core/Plugins/PluginLoader.cs index 05efe39d..cf25dffd 100644 --- a/src/Infrastructure/BotSharp.Core/Plugins/PluginLoader.cs +++ b/src/Infrastructure/BotSharp.Core/Plugins/PluginLoader.cs @@ -4,6 +4,7 @@ using Microsoft.Extensions.Configuration; using System.Drawing; using System.IO; using System.Reflection; +using System.Text.RegularExpressions; using System.Xml; namespace BotSharp.Core.Plugins; @@ -132,6 +133,12 @@ public class PluginLoader plugins = plugins.Where(x => filter.Names.Any(n => x.Name.IsEqualTo(n))).ToList(); } + if (!string.IsNullOrEmpty(filter.SimilarName)) + { + var regex = new Regex(filter.SimilarName, RegexOptions.Compiled | RegexOptions.IgnoreCase); + plugins = plugins.Where(x => regex.IsMatch(x.Name)).ToList(); + } + return new PagedItems { Items = plugins.Skip(pager.Offset).Take(pager.Size), diff --git a/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Agent.cs b/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Agent.cs index 346eebd8..7f545527 100644 --- a/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Agent.cs +++ b/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Agent.cs @@ -1,5 +1,6 @@ using BotSharp.Abstraction.Routing.Models; using System.IO; +using System.Text.RegularExpressions; namespace BotSharp.Core.Repository { @@ -62,6 +63,8 @@ namespace BotSharp.Core.Repository default: break; } + + _agents = []; } #region Update Agent Fields @@ -366,6 +369,12 @@ namespace BotSharp.Core.Repository query = query.Where(x => x.Name.ToLower() == filter.AgentName.ToLower()); } + if (!string.IsNullOrEmpty(filter.SimilarName)) + { + var regex = new Regex(filter.SimilarName, RegexOptions.Compiled | RegexOptions.IgnoreCase); + query = query.Where(x => regex.IsMatch(x.Name)); + } + if (filter.Disabled.HasValue) { query = query.Where(x => x.Disabled == filter.Disabled); @@ -478,7 +487,8 @@ namespace BotSharp.Core.Repository File.WriteAllText(instFile, agent.Instruction); } } - Reset(); + + ResetLocalAgents(); } public void BulkInsertUserAgents(List userAgents) @@ -511,7 +521,7 @@ namespace BotSharp.Core.Repository Thread.Sleep(50); } - Reset(); + ResetLocalAgents(); } public bool DeleteAgents() @@ -566,7 +576,7 @@ namespace BotSharp.Core.Repository // Delete agent folder Directory.Delete(agentDir, true); - Reset(); + ResetLocalAgents(); return true; } catch @@ -575,7 +585,7 @@ namespace BotSharp.Core.Repository } } - private void Reset() + private void ResetLocalAgents() { _agents = []; _userAgents = []; diff --git a/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Conversation.cs b/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Conversation.cs index 9f5ed57f..fd13936c 100644 --- a/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Conversation.cs +++ b/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Conversation.cs @@ -54,6 +54,7 @@ namespace BotSharp.Core.Repository Directory.Delete(convDir, true); } + return true; } diff --git a/src/Plugins/BotSharp.Plugin.KnowledgeBase/Services/KnowledgeService.Document.cs b/src/Plugins/BotSharp.Plugin.KnowledgeBase/Services/KnowledgeService.Document.cs index dd7e0d1a..8c49f48c 100644 --- a/src/Plugins/BotSharp.Plugin.KnowledgeBase/Services/KnowledgeService.Document.cs +++ b/src/Plugins/BotSharp.Plugin.KnowledgeBase/Services/KnowledgeService.Document.cs @@ -10,7 +10,8 @@ namespace BotSharp.Plugin.KnowledgeBase.Services; public partial class KnowledgeService { - public async Task UploadDocumentsToKnowledge(string collectionName, IEnumerable files) + public async Task UploadDocumentsToKnowledge(string collectionName, + IEnumerable files, ChunkOption? option = null) { var res = new UploadKnowledgeResponse { @@ -48,7 +49,7 @@ public partial class KnowledgeService { // Get document info var (contentType, bytes) = await GetFileInfo(file); - var contents = await GetFileContent(contentType, bytes); + var contents = await GetFileContent(contentType, bytes, option ?? ChunkOption.Default()); // Save document var fileId = Guid.NewGuid(); @@ -369,13 +370,13 @@ public partial class KnowledgeService } #region Read doc content - private async Task> GetFileContent(string contentType, byte[] bytes) + private async Task> GetFileContent(string contentType, byte[] bytes, ChunkOption option) { IEnumerable results = new List(); if (contentType.IsEqualTo(MediaTypeNames.Text.Plain)) { - results = await ReadTxt(bytes); + results = await ReadTxt(bytes, option); } else if (contentType.IsEqualTo(MediaTypeNames.Application.Pdf)) { @@ -385,7 +386,7 @@ public partial class KnowledgeService return results; } - private async Task> ReadTxt(byte[] bytes) + private async Task> ReadTxt(byte[] bytes, ChunkOption option) { using var stream = new MemoryStream(bytes); using var reader = new StreamReader(stream); @@ -393,12 +394,7 @@ public partial class KnowledgeService reader.Close(); stream.Close(); - var lines = TextChopper.Chop(content, new ChunkOption - { - Size = 1024, - Conjunction = 12, - SplitByWord = true, - }); + var lines = TextChopper.Chop(content, option); return lines; } diff --git a/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.Agent.cs b/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.Agent.cs index 3e6dc000..d17f7f2f 100644 --- a/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.Agent.cs +++ b/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.Agent.cs @@ -298,6 +298,11 @@ public partial class MongoRepository filters.Add(builder.Eq(x => x.Name, filter.AgentName)); } + if (!string.IsNullOrEmpty(filter.SimilarName)) + { + filters.Add(builder.Regex(x => x.Name, new BsonRegularExpression(filter.SimilarName, "i"))); + } + if (filter.Disabled.HasValue) { filters.Add(builder.Eq(x => x.Disabled, filter.Disabled.Value)); diff --git a/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.User.cs b/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.User.cs index cb857aa0..1d8dd188 100644 --- a/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.User.cs +++ b/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.User.cs @@ -18,7 +18,7 @@ public partial class MongoRepository public User? GetUserByPhone(string phone) { string phoneSecond = string.Empty; - // 如果电话号码长度小于 4,直接返回 null + // if phone number length is less than 4, return null if (phone?.Length < 4) { return null; From 8dcf894234a1d1a424b337fb493d0a9c8d26cc68 Mon Sep 17 00:00:00 2001 From: Jicheng Lu <103353@smsassist.com> Date: Fri, 15 Nov 2024 10:57:50 -0600 Subject: [PATCH 23/29] minor changes --- .../BotSharp.Abstraction/Roles/IRoleService.cs | 2 +- .../BotSharp.Abstraction/Users/IUserService.cs | 2 +- .../BotSharp.Core/Roles/Services/RoleService.cs | 4 ++-- .../BotSharp.Core/Users/Services/UserService.cs | 4 ++-- .../Controllers/KnowledgeBaseController.cs | 7 ++++--- .../BotSharp.OpenAPI/Controllers/RoleController.cs | 2 +- .../BotSharp.OpenAPI/Controllers/UserController.cs | 2 +- .../ViewModels/Knowledges/VectorKnowledgeUploadRequest.cs | 3 +++ 8 files changed, 15 insertions(+), 11 deletions(-) diff --git a/src/Infrastructure/BotSharp.Abstraction/Roles/IRoleService.cs b/src/Infrastructure/BotSharp.Abstraction/Roles/IRoleService.cs index 325c2e9c..e1fa86db 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Roles/IRoleService.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Roles/IRoleService.cs @@ -8,6 +8,6 @@ public interface IRoleService Task RefreshRoles(); Task> GetRoleOptions(); Task> GetRoles(RoleFilter filter); - Task GetRoleDetails(string roleId); + Task GetRoleDetails(string roleId, bool includeAgent = false); Task UpdateRole(Role role, bool isUpdateRoleAgents = false); } diff --git a/src/Infrastructure/BotSharp.Abstraction/Users/IUserService.cs b/src/Infrastructure/BotSharp.Abstraction/Users/IUserService.cs index 73605789..9ad2e166 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Users/IUserService.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Users/IUserService.cs @@ -8,7 +8,7 @@ public interface IUserService { Task GetUser(string id); Task> GetUsers(UserFilter filter); - Task GetUserDetails(string userId); + Task GetUserDetails(string userId, bool includeAgent = false); Task IsAdminUser(string userId); Task GetUserAuthorizations(IEnumerable? agentIds = null); Task UpdateUser(User user, bool isUpdateUserAgents = false); diff --git a/src/Infrastructure/BotSharp.Core/Roles/Services/RoleService.cs b/src/Infrastructure/BotSharp.Core/Roles/Services/RoleService.cs index 4319d06e..8cd7935b 100644 --- a/src/Infrastructure/BotSharp.Core/Roles/Services/RoleService.cs +++ b/src/Infrastructure/BotSharp.Core/Roles/Services/RoleService.cs @@ -43,10 +43,10 @@ public class RoleService : IRoleService return roles; } - public async Task GetRoleDetails(string roleId) + public async Task GetRoleDetails(string roleId, bool includeAgent = false) { var db = _services.GetRequiredService(); - var role = db.GetRoleDetails(roleId, includeAgent: true); + var role = db.GetRoleDetails(roleId, includeAgent); return role; } diff --git a/src/Infrastructure/BotSharp.Core/Users/Services/UserService.cs b/src/Infrastructure/BotSharp.Core/Users/Services/UserService.cs index 5e5572ec..9a7a4a1e 100644 --- a/src/Infrastructure/BotSharp.Core/Users/Services/UserService.cs +++ b/src/Infrastructure/BotSharp.Core/Users/Services/UserService.cs @@ -452,10 +452,10 @@ public class UserService : IUserService return auth; } - public async Task GetUserDetails(string userId) + public async Task GetUserDetails(string userId, bool includeAgent = false) { var db = _services.GetRequiredService(); - return db.GetUserDetails(userId, includeAgent: true); + return db.GetUserDetails(userId, includeAgent); } public async Task UpdateUser(User user, bool isUpdateUserAgents = false) diff --git a/src/Infrastructure/BotSharp.OpenAPI/Controllers/KnowledgeBaseController.cs b/src/Infrastructure/BotSharp.OpenAPI/Controllers/KnowledgeBaseController.cs index 3ead41b0..5a1a6bda 100644 --- a/src/Infrastructure/BotSharp.OpenAPI/Controllers/KnowledgeBaseController.cs +++ b/src/Infrastructure/BotSharp.OpenAPI/Controllers/KnowledgeBaseController.cs @@ -120,12 +120,13 @@ public class KnowledgeBaseController : ControllerBase [HttpPost("/knowledge/document/{collection}/upload")] public async Task UploadKnowledgeDocuments([FromRoute] string collection, [FromBody] VectorKnowledgeUploadRequest request) { - var response = await _knowledgeService.UploadDocumentsToKnowledge(collection, request.Files); + var response = await _knowledgeService.UploadDocumentsToKnowledge(collection, request.Files, request.ChunkOption); return response; } [HttpPost("/knowledge/document/{collection}/form-upload")] - public async Task UploadKnowledgeDocuments([FromRoute] string collection, [FromForm] IEnumerable files) + public async Task UploadKnowledgeDocuments([FromRoute] string collection, + [FromForm] IEnumerable files, [FromForm] ChunkOption? option = null) { if (files.IsNullOrEmpty()) { @@ -143,7 +144,7 @@ public class KnowledgeBaseController : ControllerBase }); } - var response = await _knowledgeService.UploadDocumentsToKnowledge(collection, docs); + var response = await _knowledgeService.UploadDocumentsToKnowledge(collection, docs, option); return response; } diff --git a/src/Infrastructure/BotSharp.OpenAPI/Controllers/RoleController.cs b/src/Infrastructure/BotSharp.OpenAPI/Controllers/RoleController.cs index d270ecbe..d4ca64fc 100644 --- a/src/Infrastructure/BotSharp.OpenAPI/Controllers/RoleController.cs +++ b/src/Infrastructure/BotSharp.OpenAPI/Controllers/RoleController.cs @@ -61,7 +61,7 @@ public class RoleController : ControllerBase [HttpGet("/role/{id}/details")] public async Task GetRoleDetails([FromRoute] string id) { - var role = await _roleService.GetRoleDetails(id); + var role = await _roleService.GetRoleDetails(id, includeAgent: true); return RoleViewModel.FromRole(role); } diff --git a/src/Infrastructure/BotSharp.OpenAPI/Controllers/UserController.cs b/src/Infrastructure/BotSharp.OpenAPI/Controllers/UserController.cs index 6bdbd721..b25be68b 100644 --- a/src/Infrastructure/BotSharp.OpenAPI/Controllers/UserController.cs +++ b/src/Infrastructure/BotSharp.OpenAPI/Controllers/UserController.cs @@ -202,7 +202,7 @@ public class UserController : ControllerBase public async Task GetUserDetails(string id) { var userService = _services.GetRequiredService(); - var user = await userService.GetUserDetails(id); + var user = await userService.GetUserDetails(id, includeAgent: true); return UserViewModel.FromUser(user); } diff --git a/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Knowledges/VectorKnowledgeUploadRequest.cs b/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Knowledges/VectorKnowledgeUploadRequest.cs index 3788f05c..0934c508 100644 --- a/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Knowledges/VectorKnowledgeUploadRequest.cs +++ b/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Knowledges/VectorKnowledgeUploadRequest.cs @@ -6,4 +6,7 @@ public class VectorKnowledgeUploadRequest { [JsonPropertyName("files")] public IEnumerable Files { get; set; } = new List(); + + [JsonPropertyName("chunk_option")] + public ChunkOption? ChunkOption { get; set; } } From 94931f7d1fe5532b46d41c64ff9ac2edaf68ce25 Mon Sep 17 00:00:00 2001 From: Jicheng Lu <103353@smsassist.com> Date: Fri, 15 Nov 2024 14:39:27 -0600 Subject: [PATCH 24/29] add exclude roles --- .../Repositories/Filters/RoleFilter.cs | 6 ++++++ .../BotSharp.Abstraction/Roles/Models/Role.cs | 5 +++++ .../Repository/FileRepository/FileRepository.Role.cs | 7 ++++++- .../Repository/MongoRepository.Role.cs | 5 +++++ 4 files changed, 22 insertions(+), 1 deletion(-) diff --git a/src/Infrastructure/BotSharp.Abstraction/Repositories/Filters/RoleFilter.cs b/src/Infrastructure/BotSharp.Abstraction/Repositories/Filters/RoleFilter.cs index 0e9e131c..35e2e1d3 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Repositories/Filters/RoleFilter.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Repositories/Filters/RoleFilter.cs @@ -1,3 +1,5 @@ +using BotSharp.Abstraction.Users.Enums; + namespace BotSharp.Abstraction.Repositories.Filters; public class RoleFilter @@ -5,6 +7,10 @@ public class RoleFilter [JsonPropertyName("names")] public IEnumerable? Names { get; set; } + [JsonPropertyName("exclude_roles")] + public IEnumerable? ExcludeRoles { get; set; } = UserConstant.AdminRoles; + + public static RoleFilter Empty() { return new RoleFilter(); diff --git a/src/Infrastructure/BotSharp.Abstraction/Roles/Models/Role.cs b/src/Infrastructure/BotSharp.Abstraction/Roles/Models/Role.cs index a0c11bc3..44d9e440 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Roles/Models/Role.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Roles/Models/Role.cs @@ -19,4 +19,9 @@ public class Role [JsonPropertyName("created_time")] public DateTime CreatedTime { get; set; } + + public override string ToString() + { + return Name; + } } diff --git a/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Role.cs b/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Role.cs index 74acc45d..c797ce5a 100644 --- a/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Role.cs +++ b/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Role.cs @@ -45,7 +45,12 @@ public partial class FileRepository // Apply filters if (!filter.Names.IsNullOrEmpty()) { - roles = roles.Where(x => filter.Names.Contains(x.Id)); + roles = roles.Where(x => filter.Names.Contains(x.Name)); + } + + if (!filter.ExcludeRoles.IsNullOrEmpty()) + { + roles = roles.Where(x => !filter.ExcludeRoles.Contains(x.Name)); } return roles.ToList(); diff --git a/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.Role.cs b/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.Role.cs index ad3cf8db..958edf0f 100644 --- a/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.Role.cs +++ b/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.Role.cs @@ -48,6 +48,11 @@ public partial class MongoRepository roleFilters.Add(roleBuilder.In(x => x.Name, filter.Names)); } + if (!filter.ExcludeRoles.IsNullOrEmpty()) + { + roleFilters.Add(roleBuilder.Nin(x => x.Name, filter.ExcludeRoles)); + } + // Search var roleDocs = _dc.Roles.Find(roleBuilder.And(roleFilters)).ToList(); var roles = roleDocs.Select(x => x.ToRole()).ToList(); From f3b395ce01f62ef6403912312d62dc33adfccfea Mon Sep 17 00:00:00 2001 From: Haiping Chen Date: Sat, 16 Nov 2024 17:03:54 +0000 Subject: [PATCH 25/29] StreamReadGroupAsync --- .../BotSharp.Core/Infrastructures/Events/RedisSubscriber.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/Infrastructure/BotSharp.Core/Infrastructures/Events/RedisSubscriber.cs b/src/Infrastructure/BotSharp.Core/Infrastructures/Events/RedisSubscriber.cs index 39bb1f91..726b9358 100644 --- a/src/Infrastructure/BotSharp.Core/Infrastructures/Events/RedisSubscriber.cs +++ b/src/Infrastructure/BotSharp.Core/Infrastructures/Events/RedisSubscriber.cs @@ -1,5 +1,4 @@ using StackExchange.Redis; -using System.Threading.Channels; namespace BotSharp.Core.Infrastructures.Events; @@ -51,7 +50,6 @@ public class RedisSubscriber : IEventSubscriber foreach (var entry in entries) { _logger.LogInformation($"Consumer {Environment.MachineName} received: {channel} {entry.Values[0].Value}"); - await db.StreamAcknowledgeAsync(channel, group, entry.Id); try { @@ -64,6 +62,10 @@ public class RedisSubscriber : IEventSubscriber { _logger.LogError($"Error processing message: {ex.Message}, event id: {channel} {entry.Id}"); } + finally + { + await db.StreamAcknowledgeAsync(channel, group, entry.Id); + } } await Task.Delay(Random.Shared.Next(1, 11) * 100); From 53b43ea648f3030f99b6e3d55688ffc0f5777926 Mon Sep 17 00:00:00 2001 From: Joanna Ren <101223@smsassist.com> Date: Sat, 16 Nov 2024 21:05:59 -0600 Subject: [PATCH 26/29] update sql validator --- .../Functions/SecondaryStagePlanFn.cs | 2 ++ .../BotSharp.Plugin.Planner/Functions/SummaryPlanFn.cs | 8 ++++++-- .../TwoStaging/Models/SummaryPlan.cs | 7 +++++++ .../functions/plan_summary.json | 6 +++++- .../templates/two_stage.summarize.liquid | 2 +- .../Functions/ExecuteQueryFn.cs | 10 +++++----- .../Functions/SqlValidateFn.cs | 6 +++--- .../functions/verify_dictionary_term.json | 8 ++++++-- .../templates/database.summarize.mysql.liquid | 2 +- .../templates/sql_statement_correctness.liquid | 2 +- 10 files changed, 37 insertions(+), 16 deletions(-) create mode 100644 src/Plugins/BotSharp.Plugin.Planner/TwoStaging/Models/SummaryPlan.cs diff --git a/src/Plugins/BotSharp.Plugin.Planner/Functions/SecondaryStagePlanFn.cs b/src/Plugins/BotSharp.Plugin.Planner/Functions/SecondaryStagePlanFn.cs index f0972ccf..be780f51 100644 --- a/src/Plugins/BotSharp.Plugin.Planner/Functions/SecondaryStagePlanFn.cs +++ b/src/Plugins/BotSharp.Plugin.Planner/Functions/SecondaryStagePlanFn.cs @@ -95,6 +95,8 @@ public class SecondaryStagePlanFn : IFunctionCallback var conv = _services.GetRequiredService(); var wholeDialogs = conv.GetDialogHistory(); + wholeDialogs.Last().Content += "\r\nOutput in JSON format."; + var completion = CompletionProvider.GetChatCompletion(_services, provider: plannerAgent.LlmConfig.Provider, model: plannerAgent.LlmConfig.Model); diff --git a/src/Plugins/BotSharp.Plugin.Planner/Functions/SummaryPlanFn.cs b/src/Plugins/BotSharp.Plugin.Planner/Functions/SummaryPlanFn.cs index 7b5134a0..2f5a1708 100644 --- a/src/Plugins/BotSharp.Plugin.Planner/Functions/SummaryPlanFn.cs +++ b/src/Plugins/BotSharp.Plugin.Planner/Functions/SummaryPlanFn.cs @@ -72,8 +72,12 @@ public class SummaryPlanFn : IFunctionCallback message.Content = summary.Content; // Validate the sql result - await fn.InvokeFunction("validate_sql", message); - + var args = JsonSerializer.Deserialize(message.FunctionArgs); + if (args.IsSqlTemplate == false) + { + await fn.InvokeFunction("validate_sql", message); + } + await HookEmitter.Emit(_services, async hook => await hook.OnPlanningCompleted(nameof(TwoStageTaskPlanner), message) ); diff --git a/src/Plugins/BotSharp.Plugin.Planner/TwoStaging/Models/SummaryPlan.cs b/src/Plugins/BotSharp.Plugin.Planner/TwoStaging/Models/SummaryPlan.cs new file mode 100644 index 00000000..459237a8 --- /dev/null +++ b/src/Plugins/BotSharp.Plugin.Planner/TwoStaging/Models/SummaryPlan.cs @@ -0,0 +1,7 @@ +namespace BotSharp.Plugin.Planner.TwoStaging.Models; + +public class SummaryPlan +{ + [JsonPropertyName("is_sql_template")] + public bool IsSqlTemplate { get; set; } = false; +} diff --git a/src/Plugins/BotSharp.Plugin.Planner/data/agents/282a7128-69a1-44b0-878c-a9159b88f3b9/functions/plan_summary.json b/src/Plugins/BotSharp.Plugin.Planner/data/agents/282a7128-69a1-44b0-878c-a9159b88f3b9/functions/plan_summary.json index 7e459503..c13bfb25 100644 --- a/src/Plugins/BotSharp.Plugin.Planner/data/agents/282a7128-69a1-44b0-878c-a9159b88f3b9/functions/plan_summary.json +++ b/src/Plugins/BotSharp.Plugin.Planner/data/agents/282a7128-69a1-44b0-878c-a9159b88f3b9/functions/plan_summary.json @@ -4,6 +4,10 @@ "parameters": { "type": "object", "properties": { + "is_sql_template": { + "type": "boolean", + "description": "If user request is to generate sql template instead of actual sql statement." + }, "related_tables": { "type": "array", "description": "table name in planning steps", @@ -13,6 +17,6 @@ } } }, - "required": [ "related_tables" ] + "required": [ "related_tables", "is_sql_template" ] } } \ No newline at end of file diff --git a/src/Plugins/BotSharp.Plugin.Planner/data/agents/282a7128-69a1-44b0-878c-a9159b88f3b9/templates/two_stage.summarize.liquid b/src/Plugins/BotSharp.Plugin.Planner/data/agents/282a7128-69a1-44b0-878c-a9159b88f3b9/templates/two_stage.summarize.liquid index a585cd23..c53abdab 100644 --- a/src/Plugins/BotSharp.Plugin.Planner/data/agents/282a7128-69a1-44b0-878c-a9159b88f3b9/templates/two_stage.summarize.liquid +++ b/src/Plugins/BotSharp.Plugin.Planner/data/agents/282a7128-69a1-44b0-878c-a9159b88f3b9/templates/two_stage.summarize.liquid @@ -1,4 +1,4 @@ -You are a planning summarizer. You will generate the final output in JSON format based on the task description, knowledge and related table structure and relationship. +You are a planning summarizer. You will generate the final output in JSON format with short explanation based on the task description, knowledge and related table structure and relationship. Requirements: {{ summary_requirements }} diff --git a/src/Plugins/BotSharp.Plugin.SqlDriver/Functions/ExecuteQueryFn.cs b/src/Plugins/BotSharp.Plugin.SqlDriver/Functions/ExecuteQueryFn.cs index af337bab..99060105 100644 --- a/src/Plugins/BotSharp.Plugin.SqlDriver/Functions/ExecuteQueryFn.cs +++ b/src/Plugins/BotSharp.Plugin.SqlDriver/Functions/ExecuteQueryFn.cs @@ -30,7 +30,7 @@ public class ExecuteQueryFn : IFunctionCallback public async Task Execute(RoleDialogModel message) { var args = JsonSerializer.Deserialize(message.FunctionArgs); - var refinedArgs = await RefineSqlStatement(message, args); + //var refinedArgs = await RefineSqlStatement(message, args); var dbHook = _services.GetRequiredService(); var dbType = dbHook.GetDatabaseType(message); @@ -38,13 +38,13 @@ public class ExecuteQueryFn : IFunctionCallback { var results = dbType.ToLower() switch { - "mysql" => RunQueryInMySql(refinedArgs.SqlStatements), - "sqlserver" => RunQueryInSqlServer(refinedArgs.SqlStatements), - "redshift" => RunQueryInRedshift(refinedArgs.SqlStatements), + "mysql" => RunQueryInMySql(args.SqlStatements), + "sqlserver" => RunQueryInSqlServer(args.SqlStatements), + "redshift" => RunQueryInRedshift(args.SqlStatements), _ => throw new NotImplementedException($"Database type {dbType} is not supported.") }; - if (refinedArgs.SqlStatements.Length == 1 && refinedArgs.SqlStatements[0].StartsWith("DROP TABLE")) + if (args.SqlStatements.Length == 1 && args.SqlStatements[0].StartsWith("DROP TABLE")) { message.Content = "Drop table successfully"; return true; diff --git a/src/Plugins/BotSharp.Plugin.SqlDriver/Functions/SqlValidateFn.cs b/src/Plugins/BotSharp.Plugin.SqlDriver/Functions/SqlValidateFn.cs index a1c4dca9..5f33a74a 100644 --- a/src/Plugins/BotSharp.Plugin.SqlDriver/Functions/SqlValidateFn.cs +++ b/src/Plugins/BotSharp.Plugin.SqlDriver/Functions/SqlValidateFn.cs @@ -41,7 +41,7 @@ public class SqlValidateFn : IFunctionCallback var dbType = dbHook.GetDatabaseType(message); var validateSql = dbType.ToLower() switch { - "mysql" => $"explain\r\n{sql}", + "mysql" => $"explain\r\n{sql.Replace("SET ", "-- SET ", StringComparison.InvariantCultureIgnoreCase).Replace(";", "; explain ").TrimEnd("explain ".ToCharArray())}", "sqlserver" => $"SET PARSEONLY ON;\r\n{sql}\r\nSET PARSEONLY OFF;", "redshift" => $"explain\r\n{sql}", _ => throw new NotImplementedException($"Database type {dbType} is not supported.") @@ -49,7 +49,7 @@ public class SqlValidateFn : IFunctionCallback var msgCopy = RoleDialogModel.From(message); msgCopy.FunctionArgs = JsonSerializer.Serialize(new ExecuteQueryArgs { - SqlStatements = new string[] { validateSql } + SqlStatements = [validateSql] }); var fn = _services.GetRequiredService(); @@ -74,7 +74,7 @@ public class SqlValidateFn : IFunctionCallback Message = "Correct SQL Statement", Data = new Dictionary { - { "original_sql", sql }, + { "original_sql", message.Content }, { "error_message", ex.Message }, { "table_structure", ddl } } diff --git a/src/Plugins/BotSharp.Plugin.SqlDriver/data/agents/6745151e-6d46-4a02-8de4-1c4f21c7da95/functions/verify_dictionary_term.json b/src/Plugins/BotSharp.Plugin.SqlDriver/data/agents/6745151e-6d46-4a02-8de4-1c4f21c7da95/functions/verify_dictionary_term.json index 871210a2..64502c62 100644 --- a/src/Plugins/BotSharp.Plugin.SqlDriver/data/agents/6745151e-6d46-4a02-8de4-1c4f21c7da95/functions/verify_dictionary_term.json +++ b/src/Plugins/BotSharp.Plugin.SqlDriver/data/agents/6745151e-6d46-4a02-8de4-1c4f21c7da95/functions/verify_dictionary_term.json @@ -1,6 +1,6 @@ { "name": "verify_dictionary_term", - "description": "Get id from dictionary table by keyword. Call this function only if need_lookup_dictionary is true and is_insert is false. You can only query one table at a time.", + "description": "Get id from dictionary table by keyword. Call this function only if need_lookup_dictionary is true, is_table_from_knowledge is true and is_insert is false. You can only query one table at a time. The table name must come from the global/domain knowledge.", "parameters": { "type": "object", "properties": { @@ -16,9 +16,13 @@ "type": "boolean", "description": "if SQL statement is inserting." }, + "is_table_from_knowledge": { + "type": "boolean", + "description": "if table is from the global/domain knowledge." + }, "tables": { "type": "array", - "description": "all related dictionary tables must be from related knowledge in the context", + "description": "all related dictionary tables must be from global/domain knowledge in the context", "items": { "type": "string", "description": "table name from related knowledge in the context" diff --git a/src/Plugins/BotSharp.Plugin.SqlDriver/data/agents/beda4c12-e1ec-4b4b-b328-3df4a6687c4f/templates/database.summarize.mysql.liquid b/src/Plugins/BotSharp.Plugin.SqlDriver/data/agents/beda4c12-e1ec-4b4b-b328-3df4a6687c4f/templates/database.summarize.mysql.liquid index 515deffd..1098bdd1 100644 --- a/src/Plugins/BotSharp.Plugin.SqlDriver/data/agents/beda4c12-e1ec-4b4b-b328-3df4a6687c4f/templates/database.summarize.mysql.liquid +++ b/src/Plugins/BotSharp.Plugin.SqlDriver/data/agents/beda4c12-e1ec-4b4b-b328-3df4a6687c4f/templates/database.summarize.mysql.liquid @@ -4,7 +4,7 @@ If not, generate the query step by step based on the planning. The query must exactly based on the provided table structure. And carefully review the foreign keys to make sure you include all the accurate information. -Note: Output should be only the sql query with sql comments that can be directly run in mysql database with version 8.0. +Note: Output should be only the sql query with short sql comments and explanation that can be directly run in mysql database with version 8.0. Don't use the sql statement that specify target table for update in FROM clause. For example, you CAN'T write query as below: diff --git a/src/Plugins/BotSharp.Plugin.SqlDriver/data/agents/beda4c12-e1ec-4b4b-b328-3df4a6687c4f/templates/sql_statement_correctness.liquid b/src/Plugins/BotSharp.Plugin.SqlDriver/data/agents/beda4c12-e1ec-4b4b-b328-3df4a6687c4f/templates/sql_statement_correctness.liquid index 2e6f28e1..f180e19f 100644 --- a/src/Plugins/BotSharp.Plugin.SqlDriver/data/agents/beda4c12-e1ec-4b4b-b328-3df4a6687c4f/templates/sql_statement_correctness.liquid +++ b/src/Plugins/BotSharp.Plugin.SqlDriver/data/agents/beda4c12-e1ec-4b4b-b328-3df4a6687c4f/templates/sql_statement_correctness.liquid @@ -1,5 +1,5 @@ You are a sql statement corrector. You will need to refer to the table structure and rewrite the original sql statement so it's using the correct information, e.g. column name. -Output the sql statement only without comments, in JSON format: {{ response_format }} +Correct the sql statement and keep only the original explanation and comments without any information related to error message{% if response_format %} in JSON format: {{ response_format }} {% endif %}. Make sure all the column names are defined in the Table Structure. ===== From ebd2688564603efdbaee6482e913502b536b67de Mon Sep 17 00:00:00 2001 From: Haiping Chen Date: Mon, 18 Nov 2024 14:15:23 +0000 Subject: [PATCH 27/29] Click on the body to give focus to the page --- .../BotSharp.Plugin.WebDriver.csproj | 4 ++-- .../Drivers/PlaywrightDriver/PlaywrightWebDriver.GoToPage.cs | 3 +++ .../Drivers/PlaywrightDriver/PlaywrightWebDriver.cs | 3 +++ 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/Plugins/BotSharp.Plugin.WebDriver/BotSharp.Plugin.WebDriver.csproj b/src/Plugins/BotSharp.Plugin.WebDriver/BotSharp.Plugin.WebDriver.csproj index d6074b19..296e17c3 100644 --- a/src/Plugins/BotSharp.Plugin.WebDriver/BotSharp.Plugin.WebDriver.csproj +++ b/src/Plugins/BotSharp.Plugin.WebDriver/BotSharp.Plugin.WebDriver.csproj @@ -20,8 +20,8 @@ - - + + diff --git a/src/Plugins/BotSharp.Plugin.WebDriver/Drivers/PlaywrightDriver/PlaywrightWebDriver.GoToPage.cs b/src/Plugins/BotSharp.Plugin.WebDriver/Drivers/PlaywrightDriver/PlaywrightWebDriver.GoToPage.cs index 30bd6748..44916a89 100644 --- a/src/Plugins/BotSharp.Plugin.WebDriver/Drivers/PlaywrightDriver/PlaywrightWebDriver.GoToPage.cs +++ b/src/Plugins/BotSharp.Plugin.WebDriver/Drivers/PlaywrightDriver/PlaywrightWebDriver.GoToPage.cs @@ -36,6 +36,9 @@ public partial class PlaywrightWebDriver includeResponseUrls: args.IncludeResponseUrls); } + // Active current tab + await page.BringToFrontAsync(); + var response = await page.GotoAsync(args.Url, new PageGotoOptions { Timeout = args.Timeout > 0 ? args.Timeout : 30000 diff --git a/src/Plugins/BotSharp.Plugin.WebDriver/Drivers/PlaywrightDriver/PlaywrightWebDriver.cs b/src/Plugins/BotSharp.Plugin.WebDriver/Drivers/PlaywrightDriver/PlaywrightWebDriver.cs index 4d13e26f..4be41140 100644 --- a/src/Plugins/BotSharp.Plugin.WebDriver/Drivers/PlaywrightDriver/PlaywrightWebDriver.cs +++ b/src/Plugins/BotSharp.Plugin.WebDriver/Drivers/PlaywrightDriver/PlaywrightWebDriver.cs @@ -75,6 +75,9 @@ public partial class PlaywrightWebDriver : IWebBrowser var page = _instance.GetPage(message.ContextId); if (page != null) { + // Click on the body to give focus to the page + await page.FocusAsync("input"); + await page.Keyboard.PressAsync(key); } } From afc880e1c0ecc3a8934ef915a762e73d0659462e Mon Sep 17 00:00:00 2001 From: Haiping Chen Date: Mon, 18 Nov 2024 18:52:59 +0000 Subject: [PATCH 28/29] DelayBeforePressingKey --- .../Browsing/Models/ElementActionArgs.cs | 4 ++++ .../PlaywrightDriver/PlaywrightWebDriver.DoAction.cs | 8 ++++++++ 2 files changed, 12 insertions(+) diff --git a/src/Infrastructure/BotSharp.Abstraction/Browsing/Models/ElementActionArgs.cs b/src/Infrastructure/BotSharp.Abstraction/Browsing/Models/ElementActionArgs.cs index 2935496c..7ac03f17 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Browsing/Models/ElementActionArgs.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Browsing/Models/ElementActionArgs.cs @@ -12,6 +12,10 @@ public class ElementActionArgs public ElementPosition? Position { get; set; } + /// + /// Delay milliseconds before pressing key + /// + public int DelayBeforePressingKey { get; set; } public string? PressKey { get; set; } /// diff --git a/src/Plugins/BotSharp.Plugin.WebDriver/Drivers/PlaywrightDriver/PlaywrightWebDriver.DoAction.cs b/src/Plugins/BotSharp.Plugin.WebDriver/Drivers/PlaywrightDriver/PlaywrightWebDriver.DoAction.cs index 93928af9..f7884beb 100644 --- a/src/Plugins/BotSharp.Plugin.WebDriver/Drivers/PlaywrightDriver/PlaywrightWebDriver.DoAction.cs +++ b/src/Plugins/BotSharp.Plugin.WebDriver/Drivers/PlaywrightDriver/PlaywrightWebDriver.DoAction.cs @@ -56,6 +56,10 @@ public partial class PlaywrightWebDriver if (action.PressKey != null) { + if (action.DelayBeforePressingKey > 0) + { + await Task.Delay(action.DelayBeforePressingKey); + } await locator.PressAsync(action.PressKey); } } @@ -64,6 +68,10 @@ public partial class PlaywrightWebDriver await locator.PressSequentiallyAsync(action.Content); if (action.PressKey != null) { + if (action.DelayBeforePressingKey > 0) + { + await Task.Delay(action.DelayBeforePressingKey); + } await locator.PressAsync(action.PressKey); } } From b01acea3a5cfa84c1b11193f5552aa0576eecfc2 Mon Sep 17 00:00:00 2001 From: Haiping Chen Date: Mon, 18 Nov 2024 20:09:47 +0000 Subject: [PATCH 29/29] Fix compile issue. --- .../Repository/MongoRepository.User.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.User.cs b/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.User.cs index 071e58f3..e999e034 100644 --- a/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.User.cs +++ b/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.User.cs @@ -2,8 +2,6 @@ using BotSharp.Abstraction.Agents.Models; using BotSharp.Abstraction.Repositories.Filters; using BotSharp.Abstraction.Users.Enums; using BotSharp.Abstraction.Users.Models; -using MongoDB.Driver; -using System.Globalization; namespace BotSharp.Plugin.MongoStorage.Repository; @@ -24,7 +22,7 @@ public partial class MongoRepository return null; } - string phoneSecond = phone.StartsWith("+86") ? phone.Replace("+86", "") : $"+86{phone}"; + phoneSecond = phone.StartsWith("+86") ? phone.Replace("+86", "") : $"+86{phone}"; var user = _dc.Users.AsQueryable().FirstOrDefault(x => (x.Phone == phone || x.Phone == phoneSecond) && x.Type != UserType.Affiliate && (x.RegionCode == regionCode || string.IsNullOrWhiteSpace(x.RegionCode))