diff --git a/Directory.Build.props b/Directory.Build.props index 4233fb32..6716a3c7 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -2,7 +2,7 @@ net8.0 12.0 - 3.0.0 + 4.0.0 true false diff --git a/README.md b/README.md index e5c3684a..f8f76eb4 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ It's written in C# running on .Net Core that is full cross-platform framework, t * Built-in multi-agents and conversation with state management. * Support multiple LLM Planning approaches to handle different tasks from simple to complex. * Built-in RAG related interfaces, Memory based vector searching. -* Support multiple AI platforms (ChatGPT 3.5 / 4.0, PaLM 2, LLaMA 3, Claude Sonnet 3.5, HuggingFace). +* Support multiple AI platforms (ChatGPT 3.5/ 4o/ o1, Gemini 2, LLaMA 3, Claude Sonnet 3.5, HuggingFace). * Allow multiple agents with different responsibilities cooperate to complete complex tasks. * Build, test, evaluate and audit your LLM agent in one place. * Build-in `BotSharp UI` written in [SvelteKit](https://kit.svelte.dev/). @@ -79,6 +79,7 @@ BotSharp uses component design, the kernel is kept to a minimum, and business fu #### Data Storages - BotSharp.Core.Repository - BotSharp.Plugin.MongoStorage +- [BotSharp.Plugin.LiteDBStorage](https://github.com/GreenShadeZhang/BotSharp/tree/dev_litedb/src/Plugins/BotSharp.Plugin.LiteDBStorage) - BotSharp.Plugin.TencentCos #### LLMs diff --git a/src/Infrastructure/BotSharp.Abstraction/Agents/Enums/AgentField.cs b/src/Infrastructure/BotSharp.Abstraction/Agents/Enums/AgentField.cs index 346bcf31..2a9508d7 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Agents/Enums/AgentField.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Agents/Enums/AgentField.cs @@ -19,6 +19,7 @@ public enum AgentField LlmConfig, Utility, KnowledgeBase, + Rule, MaxMessageCount } diff --git a/src/Infrastructure/BotSharp.Abstraction/Agents/IAgentRuleHook.cs b/src/Infrastructure/BotSharp.Abstraction/Agents/IAgentRuleHook.cs new file mode 100644 index 00000000..8a19a561 --- /dev/null +++ b/src/Infrastructure/BotSharp.Abstraction/Agents/IAgentRuleHook.cs @@ -0,0 +1,6 @@ +namespace BotSharp.Abstraction.Agents; + +public interface IAgentRuleHook +{ + void AddRules(List rules); +} diff --git a/src/Infrastructure/BotSharp.Abstraction/Agents/IAgentService.cs b/src/Infrastructure/BotSharp.Abstraction/Agents/IAgentService.cs index 91beea8b..2ec4ff92 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Agents/IAgentService.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Agents/IAgentService.cs @@ -58,6 +58,4 @@ public interface IAgentService Task> GetUserAgents(string userId); PluginDef GetPlugin(string agentId); - - IEnumerable GetAgentUtilityOptions(); } diff --git a/src/Infrastructure/BotSharp.Abstraction/Agents/Models/Agent.cs b/src/Infrastructure/BotSharp.Abstraction/Agents/Models/Agent.cs index 566df050..96aa070c 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Agents/Models/Agent.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Agents/Models/Agent.cs @@ -99,6 +99,11 @@ public class Agent /// public List Utilities { get; set; } = new(); + /// + /// Agent rules + /// + public List Rules { get; set; } = new(); + /// /// Agent knowledge bases /// @@ -154,6 +159,7 @@ public class Agent MaxMessageCount = agent.MaxMessageCount, Profiles = agent.Profiles, RoutingRules = agent.RoutingRules, + Rules = agent.Rules, LlmConfig = agent.LlmConfig, KnowledgeBases = agent.KnowledgeBases, CreatedDateTime = agent.CreatedDateTime, @@ -269,6 +275,12 @@ public class Agent return this; } + public Agent SetRules(List rules) + { + Rules = rules ?? []; + return this; + } + public Agent SetLlmConfig(AgentLlmConfig? llmConfig) { LlmConfig = llmConfig; diff --git a/src/Infrastructure/BotSharp.Abstraction/Agents/Models/AgentRule.cs b/src/Infrastructure/BotSharp.Abstraction/Agents/Models/AgentRule.cs new file mode 100644 index 00000000..5c3a276e --- /dev/null +++ b/src/Infrastructure/BotSharp.Abstraction/Agents/Models/AgentRule.cs @@ -0,0 +1,13 @@ +namespace BotSharp.Abstraction.Agents.Models; + +public class AgentRule +{ + public string Name { get; set; } + public bool Disabled { get; set; } + + [JsonPropertyName("event_name")] + public string EventName { get; set; } + + [JsonPropertyName("entity_type")] + public string EntityType { get; set; } +} diff --git a/src/Infrastructure/BotSharp.Abstraction/Browsing/IWebBrowser.cs b/src/Infrastructure/BotSharp.Abstraction/Browsing/IWebBrowser.cs index ff2c4a7d..9386dd8a 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Browsing/IWebBrowser.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Browsing/IWebBrowser.cs @@ -28,4 +28,5 @@ public interface IWebBrowser Task CloseCurrentPage(MessageInfo message); Task SendHttpRequest(MessageInfo message, HttpRequestParams actionParams); Task GetAttributeValue(MessageInfo message, ElementLocatingArgs location); + Task SetAttributeValue(MessageInfo message, ElementLocatingArgs location); } diff --git a/src/Infrastructure/BotSharp.Abstraction/Browsing/Models/BrowserActionArgs.cs b/src/Infrastructure/BotSharp.Abstraction/Browsing/Models/BrowserActionArgs.cs index 21bdf6ab..7987b3a5 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Browsing/Models/BrowserActionArgs.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Browsing/Models/BrowserActionArgs.cs @@ -3,4 +3,6 @@ namespace BotSharp.Abstraction.Browsing.Models; public class BrowserActionArgs { public bool Headless { get; set; } + public string? UserDataDir { get; set; } + public string? RemoteHostUrl { get; set; } } diff --git a/src/Infrastructure/BotSharp.Abstraction/Browsing/Models/BrowserActionResult.cs b/src/Infrastructure/BotSharp.Abstraction/Browsing/Models/BrowserActionResult.cs index 1867a687..e2f11972 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Browsing/Models/BrowserActionResult.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Browsing/Models/BrowserActionResult.cs @@ -8,6 +8,10 @@ public class BrowserActionResult public string? StackTrace { get; set; } public string? Selector { get; set; } public string? Body { get; set; } + /// + /// Page open in new tab after button click + /// + public string? UrlAfterAction { get; set; } public bool IsHighlighted { get; set; } public override string ToString() diff --git a/src/Infrastructure/BotSharp.Abstraction/Http/IHttpRequestHook.cs b/src/Infrastructure/BotSharp.Abstraction/Http/IHttpRequestHook.cs new file mode 100644 index 00000000..18b96e77 --- /dev/null +++ b/src/Infrastructure/BotSharp.Abstraction/Http/IHttpRequestHook.cs @@ -0,0 +1,8 @@ +using System.Net.Http.Headers; + +namespace BotSharp.Abstraction.Http; + +public interface IHttpRequestHook +{ + void OnAddHttpHeaders(HttpHeaders headers); +} diff --git a/src/Infrastructure/BotSharp.Abstraction/Repositories/IBotSharpRepository.cs b/src/Infrastructure/BotSharp.Abstraction/Repositories/IBotSharpRepository.cs index 4391f970..70fecc8a 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Repositories/IBotSharpRepository.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Repositories/IBotSharpRepository.cs @@ -33,6 +33,7 @@ public interface IBotSharpRepository : IHaveServiceProvider List GetUserByIds(List ids) => throw new NotImplementedException(); List GetUsersByAffiliateId(string affiliateId) => throw new NotImplementedException(); User? GetUserByUserName(string userName) => throw new NotImplementedException(); + void UpdateUserName(string userId, string userName) => throw new NotImplementedException(); Dashboard? GetDashboard(string id = null) => throw new NotImplementedException(); void CreateUser(User user) => throw new NotImplementedException(); void UpdateExistUser(string userId, User user) => throw new NotImplementedException(); @@ -49,6 +50,7 @@ public interface IBotSharpRepository : IHaveServiceProvider PagedItems GetUsers(UserFilter filter) => 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/Users/IAuthenticationHook.cs b/src/Infrastructure/BotSharp.Abstraction/Users/IAuthenticationHook.cs index d953271f..939d509e 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Users/IAuthenticationHook.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Users/IAuthenticationHook.cs @@ -1,5 +1,4 @@ using BotSharp.Abstraction.Users.Models; -using System.IdentityModel.Tokens.Jwt; using System.Security.Claims; namespace BotSharp.Abstraction.Users; diff --git a/src/Infrastructure/BotSharp.Abstraction/Users/IUserService.cs b/src/Infrastructure/BotSharp.Abstraction/Users/IUserService.cs index 134ca25b..5e56b431 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Users/IUserService.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Users/IUserService.cs @@ -17,6 +17,7 @@ public interface IUserService Task GetAffiliateToken(string authorization); Task GetAdminToken(string authorization); Task GetToken(string authorization); + Task CreateTokenByUser(User user); Task GetMyProfile(); Task VerifyUserNameExisting(string userName); Task VerifyEmailExisting(string email); diff --git a/src/Infrastructure/BotSharp.Abstraction/Users/Models/User.cs b/src/Infrastructure/BotSharp.Abstraction/Users/Models/User.cs index 7699a58b..8d136dc5 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Users/Models/User.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Users/Models/User.cs @@ -23,7 +23,6 @@ public class User public bool Verified { get; set; } public string RegionCode { get; set; } = "CN"; public string? AffiliateId { get; set; } - public string? ReferralCode { get; set; } public string? EmployeeId { get; set; } public bool IsDisabled { get; set; } public IEnumerable Permissions { get; set; } = []; diff --git a/src/Infrastructure/BotSharp.Abstraction/Users/Models/UserActivationModel.cs b/src/Infrastructure/BotSharp.Abstraction/Users/Models/UserActivationModel.cs index 904bd936..3ea4a915 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Users/Models/UserActivationModel.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Users/Models/UserActivationModel.cs @@ -4,4 +4,5 @@ public class UserActivationModel { public string UserName { get; set; } public string VerificationCode { get; set; } + public string RegionCode { get; set; } = "CN"; } diff --git a/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.UpdateAgent.cs b/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.UpdateAgent.cs index 584a4c7e..6889336a 100644 --- a/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.UpdateAgent.cs +++ b/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.UpdateAgent.cs @@ -40,6 +40,7 @@ public partial class AgentService record.Samples = agent.Samples ?? []; record.Utilities = agent.Utilities ?? []; record.KnowledgeBases = agent.KnowledgeBases ?? []; + record.Rules = agent.Rules ?? []; if (agent.LlmConfig != null && !agent.LlmConfig.IsInherit) { record.LlmConfig = agent.LlmConfig; @@ -104,6 +105,7 @@ public partial class AgentService .SetSamples(foundAgent.Samples) .SetUtilities(foundAgent.Utilities) .SetKnowledgeBases(foundAgent.KnowledgeBases) + .SetRules(foundAgent.Rules) .SetLlmConfig(foundAgent.LlmConfig); _db.UpdateAgent(clonedAgent, AgentField.All); diff --git a/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.cs b/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.cs index 80cdc56c..c54c4fd8 100644 --- a/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.cs +++ b/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.cs @@ -53,15 +53,4 @@ public partial class AgentService : IAgentService var userAgents = _db.GetUserAgents(userId); return userAgents; } - - public IEnumerable GetAgentUtilityOptions() - { - var utilities = new List(); - var hooks = _services.GetServices(); - foreach (var hook in hooks) - { - hook.AddUtilities(utilities); - } - return utilities.Where(x => !string.IsNullOrWhiteSpace(x.Name)).OrderBy(x => x.Name).ToList(); - } } diff --git a/src/Infrastructure/BotSharp.Core/BotSharp.Core.csproj b/src/Infrastructure/BotSharp.Core/BotSharp.Core.csproj index 1c9955aa..d1232c5e 100644 --- a/src/Infrastructure/BotSharp.Core/BotSharp.Core.csproj +++ b/src/Infrastructure/BotSharp.Core/BotSharp.Core.csproj @@ -190,10 +190,10 @@ - + - + diff --git a/src/Infrastructure/BotSharp.Core/BotSharpCoreExtensions.cs b/src/Infrastructure/BotSharp.Core/BotSharpCoreExtensions.cs index af8a9324..f588b4d1 100644 --- a/src/Infrastructure/BotSharp.Core/BotSharpCoreExtensions.cs +++ b/src/Infrastructure/BotSharp.Core/BotSharpCoreExtensions.cs @@ -105,6 +105,11 @@ public static class BotSharpCoreExtensions var dbSettings = new BotSharpDatabaseSettings(); config.Bind("Database", dbSettings); + if (string.IsNullOrEmpty(dbSettings.Redis)) + { + return; + } + services.AddSingleton(ConnectionMultiplexer.Connect(dbSettings.Redis)); services.AddSingleton(); services.AddSingleton(); diff --git a/src/Infrastructure/BotSharp.Core/Infrastructures/DistributedLocker.cs b/src/Infrastructure/BotSharp.Core/Infrastructures/DistributedLocker.cs index 48ff193f..c87dbe45 100644 --- a/src/Infrastructure/BotSharp.Core/Infrastructures/DistributedLocker.cs +++ b/src/Infrastructure/BotSharp.Core/Infrastructures/DistributedLocker.cs @@ -6,12 +6,12 @@ namespace BotSharp.Core.Infrastructures; public class DistributedLocker : IDistributedLocker { - private readonly IConnectionMultiplexer _redis; + private readonly IServiceProvider _services; private readonly ILogger _logger; - public DistributedLocker(IConnectionMultiplexer redis, ILogger logger) + public DistributedLocker(IServiceProvider services, ILogger logger) { - _redis = redis; + _services = services; _logger = logger; } @@ -19,7 +19,8 @@ public class DistributedLocker : IDistributedLocker { var timeout = TimeSpan.FromSeconds(timeoutInSeconds); - var @lock = new RedisDistributedLock(resource, _redis.GetDatabase()); + var redis = _services.GetRequiredService(); + var @lock = new RedisDistributedLock(resource, redis.GetDatabase()); await using (var handle = await @lock.TryAcquireAsync(timeout)) { if (handle == null) @@ -37,7 +38,8 @@ public class DistributedLocker : IDistributedLocker { var timeout = TimeSpan.FromSeconds(timeoutInSeconds); - var @lock = new RedisDistributedLock(resource, _redis.GetDatabase()); + var redis = _services.GetRequiredService(); + var @lock = new RedisDistributedLock(resource, redis.GetDatabase()); using (var handle = @lock.TryAcquire(timeout)) { if (handle == null) diff --git a/src/Infrastructure/BotSharp.Core/Infrastructures/Events/RedisPublisher.cs b/src/Infrastructure/BotSharp.Core/Infrastructures/Events/RedisPublisher.cs index 89de2453..cee5cc6f 100644 --- a/src/Infrastructure/BotSharp.Core/Infrastructures/Events/RedisPublisher.cs +++ b/src/Infrastructure/BotSharp.Core/Infrastructures/Events/RedisPublisher.cs @@ -39,10 +39,7 @@ public class RedisPublisher : IEventPublisher // Add a message to the stream, keeping only the latest 1 million messages var messageId = await db.StreamAddAsync(channel, - [ - new NameValueEntry("message", message), - new NameValueEntry("timestamp", DateTime.UtcNow.ToString("o")) - ], + AssembleMessage(message), maxLength: 1000 * 10000); _logger.LogInformation($"Published message {channel} {message} ({messageId})"); @@ -82,6 +79,25 @@ public class RedisPublisher : IEventPublisher return exists; } + public static NameValueEntry[] AssembleMessage(RedisValue message) + { + return + [ + new NameValueEntry("message", message), + new NameValueEntry("timestamp", DateTime.UtcNow.ToString("o")) + ]; + } + + public static NameValueEntry[] AssembleErrorMessage(RedisValue message, string error) + { + return + [ + new NameValueEntry("message", message), + new NameValueEntry("timestamp", DateTime.UtcNow.ToString("o")), + new NameValueEntry("error", error) + ]; + } + public async Task ReDispatchAsync(string channel, int count = 10, string order = "asc") { var db = _redis.GetDatabase(); @@ -93,10 +109,10 @@ public class RedisPublisher : IEventPublisher try { - var messageId = await db.StreamAddAsync(channel, [ - new NameValueEntry("message", entry.Values[0].Value), - new NameValueEntry("timestamp", DateTime.UtcNow.ToString("o")) - ]); + var message = entry.Values.First(x => x.Name == "message").Value; + var messageId = await db.StreamAddAsync(channel, + AssembleMessage(message), + maxLength: 1000 * 10000); _logger.LogWarning($"ReDispatched message: {channel} {entry.Values[0].Value} ({messageId})"); @@ -164,20 +180,7 @@ public class RedisPublisher : IEventPublisher var db = _redis.GetDatabase(); var entries = await db.StreamRangeAsync(channel, "-", "+", count: count, messageOrder: Order.Ascending); - foreach (var entry in entries) - { - _logger.LogInformation($"Fetched message: {channel} {entry.Values[0].Value} ({entry.Id})"); - - try - { - await db.StreamDeleteAsync(channel, [entry.Id]); - - _logger.LogWarning($"Deleted message: {channel} {entry.Values[0].Value} ({entry.Id})"); - } - catch (Exception ex) - { - _logger.LogError($"Error processing message: {ex.Message}, event id: {channel} {entry.Id}\r\n{ex}"); - } - } + var deletedCount = await db.StreamDeleteAsync(channel, entries.Select(x => x.Id).ToArray()); + _logger.LogWarning($"Deleted {deletedCount} messages from Redis stream {channel}"); } } diff --git a/src/Infrastructure/BotSharp.Core/Infrastructures/Events/RedisSubscriber.cs b/src/Infrastructure/BotSharp.Core/Infrastructures/Events/RedisSubscriber.cs index 9a3c9982..61db1d33 100644 --- a/src/Infrastructure/BotSharp.Core/Infrastructures/Events/RedisSubscriber.cs +++ b/src/Infrastructure/BotSharp.Core/Infrastructures/Events/RedisSubscriber.cs @@ -42,6 +42,8 @@ public class RedisSubscriber : IEventSubscriber await CreateConsumerGroup(db, channel, group); } + await CreateConsumerGroup(db, $"{channel}-Error", group); + var consumer = Environment.MachineName; if (port.HasValue) { @@ -58,28 +60,36 @@ public class RedisSubscriber : IEventSubscriber break; } - if (priorityEnabled) + try { - if (await HandleGroupMessage(db, $"{channel}-{EventPriority.High}", group, consumer, received) > 0) + if (priorityEnabled) { - continue; - } + if (await HandleGroupMessage(db, $"{channel}-{EventPriority.High}", group, consumer, received, $"{channel}-Error") > 0) + { + continue; + } - if (await HandleGroupMessage(db, $"{channel}-{EventPriority.Medium}", group, consumer, received) > 0) + if (await HandleGroupMessage(db, $"{channel}-{EventPriority.Medium}", group, consumer, received, $"{channel}-Error") > 0) + { + continue; + } + + await HandleGroupMessage(db, $"{channel}-{EventPriority.Low}", group, consumer, received, $"{channel}-Error"); + } + else { - continue; + await HandleGroupMessage(db, channel, group, consumer, received, $"{channel}-Error"); } - - await HandleGroupMessage(db, $"{channel}-{EventPriority.Low}", group, consumer, received); } - else + catch (Exception ex) { - await HandleGroupMessage(db, channel, group, consumer, received); + _logger.LogError($"Error processing message: {ex.Message}\r\n{ex}"); + await Task.Delay(1000 * 60); } } } - private async Task HandleGroupMessage(IDatabase db, string channel, string group, string consumer, Func received) + private async Task HandleGroupMessage(IDatabase db, string channel, string group, string consumer, Func received, string errorChannel) { var entries = await db.StreamReadGroupAsync(channel, group, consumer, count: 1); foreach (var entry in entries) @@ -90,13 +100,24 @@ public class RedisSubscriber : IEventSubscriber 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}\r\n{ex}"); + _logger.LogError($"Error processing message: {ex.Message}, event id: {channel} {entry.Id} {entry.Values[0].Value}"); + + // Add a message to the Error stream, keeping only the latest 1 million messages + await db.StreamAddAsync(errorChannel, + RedisPublisher.AssembleErrorMessage(entry.Values[0].Value, ex.Message), + messageId: entry.Id, + maxLength: 1000 * 10000); + + // Slow down the consumer if there are errors + await Task.Delay(1000 * 10); + } + finally + { + var deletedCount = await db.StreamDeleteAsync(channel, [entry.Id]); + _logger.LogInformation($"Handled message {entry.Id}: {deletedCount == 1}"); } } diff --git a/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Agent.cs b/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Agent.cs index 0a8d5b65..d38b2a89 100644 --- a/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Agent.cs +++ b/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Agent.cs @@ -60,6 +60,9 @@ namespace BotSharp.Core.Repository case AgentField.KnowledgeBase: UpdateAgentKnowledgeBases(agent.Id, agent.KnowledgeBases); break; + case AgentField.Rule: + UpdateAgentRules(agent.Id, agent.Rules); + break; case AgentField.MaxMessageCount: UpdateAgentMaxMessageCount(agent.Id, agent.MaxMessageCount); break; @@ -184,6 +187,19 @@ namespace BotSharp.Core.Repository File.WriteAllText(agentFile, json); } + private void UpdateAgentRules(string agentId, List rules) + { + if (rules == null) return; + + var (agent, agentFile) = GetAgentFromFile(agentId); + if (agent == null) return; + + agent.Rules = rules; + agent.UpdatedDateTime = DateTime.UtcNow; + var json = JsonSerializer.Serialize(agent, _options); + File.WriteAllText(agentFile, json); + } + private void UpdateAgentRoutingRules(string agentId, List rules) { if (rules == null) return; @@ -240,7 +256,7 @@ namespace BotSharp.Core.Repository var text = JsonSerializer.Serialize(func, _options); var file = Path.Combine(functionDir, $"{func.Name}.json"); File.WriteAllText(file, text); - Thread.Sleep(100); + Thread.Sleep(50); } } @@ -328,6 +344,7 @@ namespace BotSharp.Core.Repository agent.Utilities = inputAgent.Utilities; agent.KnowledgeBases = inputAgent.KnowledgeBases; agent.RoutingRules = inputAgent.RoutingRules; + agent.Rules = inputAgent.Rules; agent.LlmConfig = inputAgent.LlmConfig; agent.MaxMessageCount = inputAgent.MaxMessageCount; agent.UpdatedDateTime = DateTime.UtcNow; diff --git a/src/Infrastructure/BotSharp.Core/Users/Services/UserService.cs b/src/Infrastructure/BotSharp.Core/Users/Services/UserService.cs index 9bc436a7..18deb7d8 100644 --- a/src/Infrastructure/BotSharp.Core/Users/Services/UserService.cs +++ b/src/Infrastructure/BotSharp.Core/Users/Services/UserService.cs @@ -62,7 +62,7 @@ public class UserService : IUserService if (!string.IsNullOrWhiteSpace(user.Phone)) { - record = db.GetUserByPhone(user.Phone); + record = db.GetUserByPhone(user.Phone, regionCode: (string.IsNullOrWhiteSpace(user.RegionCode) ? "CN" : user.RegionCode)); } if (record == null && !string.IsNullOrWhiteSpace(user.Email)) @@ -112,7 +112,7 @@ public class UserService : IUserService db.UpdateExistUser(hasRegisterId, record); } - _logger.LogWarning($"Created new user account: {record.Id} {record.UserName}"); + _logger.LogWarning($"Created new user account: {record.Id} {record.UserName}, RegionCode: {record.RegionCode}"); Utilities.ClearCache(); var hooks = _services.GetServices(); @@ -127,7 +127,8 @@ public class UserService : IUserService public async Task UpdatePassword(string password, string verificationCode) { var db = _services.GetRequiredService(); - var record = db.GetUserByUserName(_user.UserName); + + var record = db.GetUserById(_user.Id); if (record == null) { @@ -473,7 +474,7 @@ public class UserService : IUserService var record = id.Contains("@") ? db.GetUserByEmail(id) : db.GetUserByUserName(id); if (record == null) { - record = db.GetUserByPhone(id); + record = db.GetUserByPhone(id, regionCode: (string.IsNullOrWhiteSpace(model.RegionCode) ? "CN" : model.RegionCode)); } if (record == null) @@ -505,6 +506,20 @@ public class UserService : IUserService return token; } + public async Task CreateTokenByUser(User user) + { + var accessToken = GenerateJwtToken(user); + var jwt = new JwtSecurityTokenHandler().ReadJwtToken(accessToken); + var token = new Token + { + AccessToken = accessToken, + ExpireTime = jwt.Payload.Exp.Value, + TokenType = "Bearer", + Scope = "api" + }; + return token; + } + public async Task VerifyUserNameExisting(string userName) { if (string.IsNullOrEmpty(userName)) @@ -646,7 +661,7 @@ public class UserService : IUserService if (!string.IsNullOrEmpty(user.Phone)) { - record = db.GetUserByPhone(user.Phone); + record = db.GetUserByPhone(user.Phone, regionCode: (string.IsNullOrWhiteSpace(user.RegionCode) ? "CN" : user.RegionCode)); } if (record == null) @@ -695,7 +710,7 @@ public class UserService : IUserService var curUser = await GetMyProfile(); var db = _services.GetRequiredService(); var record = db.GetUserById(curUser.Id); - var existPhone = db.GetUserByPhone(phone); + var existPhone = db.GetUserByPhone(phone, regionCode: regionCode); if (record == null || (existPhone != null && existPhone.RegionCode == regionCode)) { diff --git a/src/Infrastructure/BotSharp.OpenAPI/Controllers/AgentController.cs b/src/Infrastructure/BotSharp.OpenAPI/Controllers/AgentController.cs index 1885365d..6efb7104 100644 --- a/src/Infrastructure/BotSharp.OpenAPI/Controllers/AgentController.cs +++ b/src/Infrastructure/BotSharp.OpenAPI/Controllers/AgentController.cs @@ -152,6 +152,24 @@ public class AgentController : ControllerBase [HttpGet("/agent/utility/options")] public IEnumerable GetAgentUtilityOptions() { - return _agentService.GetAgentUtilityOptions(); + var utilities = new List(); + var hooks = _services.GetServices(); + foreach (var hook in hooks) + { + hook.AddUtilities(utilities); + } + return utilities.Where(x => !string.IsNullOrWhiteSpace(x.Name)).OrderBy(x => x.Name).ToList(); + } + + [HttpGet("/agent/rule/options")] + public IEnumerable GetAgentRuleOptions() + { + var rules = new List(); + var hooks = _services.GetServices(); + foreach (var hook in hooks) + { + hook.AddRules(rules); + } + return rules.Where(x => !string.IsNullOrWhiteSpace(x.Name)).OrderBy(x => x.Name).ToList(); } } \ No newline at end of file diff --git a/src/Infrastructure/BotSharp.OpenAPI/Controllers/UserController.cs b/src/Infrastructure/BotSharp.OpenAPI/Controllers/UserController.cs index 9d652a3e..75b33c73 100644 --- a/src/Infrastructure/BotSharp.OpenAPI/Controllers/UserController.cs +++ b/src/Infrastructure/BotSharp.OpenAPI/Controllers/UserController.cs @@ -1,4 +1,3 @@ -using BotSharp.Abstraction.Users.Enums; using BotSharp.Abstraction.Users.Settings; using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authentication.Cookies; @@ -244,7 +243,7 @@ public class UserController : ControllerBase var file = fileStorage.GetUserAvatar(); if (string.IsNullOrEmpty(file)) { - return NotFound(); + return NoContent(); } return BuildFileResult(file); } diff --git a/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Agents/AgentCreationModel.cs b/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Agents/AgentCreationModel.cs index 3caba6ae..a0e8f790 100644 --- a/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Agents/AgentCreationModel.cs +++ b/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Agents/AgentCreationModel.cs @@ -55,6 +55,7 @@ public class AgentCreationModel public List Utilities { get; set; } = new(); public List RoutingRules { get; set; } = new(); public List KnowledgeBases { get; set; } = new(); + public List Rules { get; set; } = new(); public AgentLlmConfig? LlmConfig { get; set; } public Agent ToAgent() @@ -78,6 +79,7 @@ public class AgentCreationModel Profiles = Profiles, LlmConfig = LlmConfig, KnowledgeBases = KnowledgeBases, + Rules = Rules, RoutingRules = RoutingRules?.Select(x => RoutingRuleUpdateModel.ToDomainElement(x))?.ToList() ?? [], }; } diff --git a/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Agents/AgentUpdateModel.cs b/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Agents/AgentUpdateModel.cs index 66618186..8e268b97 100644 --- a/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Agents/AgentUpdateModel.cs +++ b/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Agents/AgentUpdateModel.cs @@ -75,6 +75,9 @@ public class AgentUpdateModel [JsonPropertyName("routing_rules")] public List? RoutingRules { get; set; } + [JsonPropertyName("rules")] + public List? Rules { get; set; } + [JsonPropertyName("llm_config")] public AgentLlmConfig? LlmConfig { get; set; } @@ -89,15 +92,16 @@ public class AgentUpdateModel MergeUtility = MergeUtility, MaxMessageCount = MaxMessageCount, Type = Type, - Profiles = Profiles ?? new List(), - RoutingRules = RoutingRules?.Select(x => RoutingRuleUpdateModel.ToDomainElement(x))?.ToList() ?? new List(), + Profiles = Profiles ?? [], + RoutingRules = RoutingRules?.Select(x => RoutingRuleUpdateModel.ToDomainElement(x))?.ToList() ?? [], Instruction = Instruction ?? string.Empty, - ChannelInstructions = ChannelInstructions ?? new List(), - Templates = Templates ?? new List(), - Functions = Functions ?? new List(), - Responses = Responses ?? new List(), - Utilities = Utilities ?? new List(), + ChannelInstructions = ChannelInstructions ?? [], + Templates = Templates ?? [], + Functions = Functions ?? [], + Responses = Responses ?? [], + Utilities = Utilities ?? [], KnowledgeBases = KnowledgeBases ?? [], + Rules = Rules ?? [], LlmConfig = LlmConfig }; diff --git a/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Agents/AgentViewModel.cs b/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Agents/AgentViewModel.cs index e9479109..de4a13ba 100644 --- a/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Agents/AgentViewModel.cs +++ b/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Agents/AgentViewModel.cs @@ -28,6 +28,9 @@ public class AgentViewModel [JsonPropertyName("knowledge_bases")] public List KnowledgeBases { get; set; } + [JsonPropertyName("rules")] + public List Rules { get; set; } + [JsonPropertyName("is_public")] public bool IsPublic { get; set; } @@ -72,20 +75,21 @@ public class AgentViewModel Description = agent.Description, Type = agent.Type, Instruction = agent.Instruction, - ChannelInstructions = agent.ChannelInstructions, - Templates = agent.Templates, - Functions = agent.Functions, - Responses = agent.Responses, - Samples = agent.Samples, - Utilities = agent.Utilities, - KnowledgeBases = agent.KnowledgeBases, + ChannelInstructions = agent.ChannelInstructions ?? [], + Templates = agent.Templates ?? [], + Functions = agent.Functions ?? [], + Responses = agent.Responses ?? [], + Samples = agent.Samples ?? [], + Utilities = agent.Utilities ?? [], + KnowledgeBases = agent.KnowledgeBases ?? [], IsPublic= agent.IsPublic, Disabled = agent.Disabled, MergeUtility = agent.MergeUtility, IconUrl = agent.IconUrl, MaxMessageCount = agent.MaxMessageCount, - Profiles = agent.Profiles ?? new List(), - RoutingRules = agent.RoutingRules, + Profiles = agent.Profiles ?? [], + RoutingRules = agent.RoutingRules ?? [], + Rules = agent.Rules ?? [], LlmConfig = agent.LlmConfig, Plugin = agent.Plugin, CreatedDateTime = agent.CreatedDateTime, diff --git a/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Users/UserCreationModel.cs b/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Users/UserCreationModel.cs index b529a832..4d2594b8 100644 --- a/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Users/UserCreationModel.cs +++ b/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Users/UserCreationModel.cs @@ -13,7 +13,6 @@ public class UserCreationModel public string Type { get; set; } = UserType.Client; public string Role { get; set; } = UserRole.User; public string RegionCode { get; set; } = "CN"; - public string? ReferralCode { get; set; } public User ToUser() { return new User @@ -27,7 +26,6 @@ public class UserCreationModel Role = Role, Type = Type, RegionCode = RegionCode, - ReferralCode = ReferralCode }; } } diff --git a/src/Plugins/BotSharp.Plugin.HttpHandler/BotSharp.Plugin.HttpHandler.csproj b/src/Plugins/BotSharp.Plugin.HttpHandler/BotSharp.Plugin.HttpHandler.csproj index 7dc0ac87..61ab8648 100644 --- a/src/Plugins/BotSharp.Plugin.HttpHandler/BotSharp.Plugin.HttpHandler.csproj +++ b/src/Plugins/BotSharp.Plugin.HttpHandler/BotSharp.Plugin.HttpHandler.csproj @@ -33,10 +33,6 @@ - - - - diff --git a/src/Plugins/BotSharp.Plugin.HttpHandler/Functions/HandleHttpRequestFn.cs b/src/Plugins/BotSharp.Plugin.HttpHandler/Functions/HandleHttpRequestFn.cs index d36aafba..52f5075b 100644 --- a/src/Plugins/BotSharp.Plugin.HttpHandler/Functions/HandleHttpRequestFn.cs +++ b/src/Plugins/BotSharp.Plugin.HttpHandler/Functions/HandleHttpRequestFn.cs @@ -1,5 +1,6 @@ using System.Net.Http; using System.Net.Mime; +using BotSharp.Abstraction.Http; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Logging; @@ -13,7 +14,6 @@ public class HandleHttpRequestFn : IFunctionCallback private readonly IServiceProvider _services; private readonly ILogger _logger; private readonly IHttpClientFactory _httpClientFactory; - private readonly IHttpContextAccessor _context; private readonly BotSharpOptions _options; public HandleHttpRequestFn(IServiceProvider services, @@ -25,7 +25,6 @@ public class HandleHttpRequestFn : IFunctionCallback _services = services; _logger = logger; _httpClientFactory = httpClientFactory; - _context = context; _options = options; } @@ -46,7 +45,7 @@ public class HandleHttpRequestFn : IFunctionCallback catch (Exception ex) { var msg = $"Fail when sending http request. Url: {url}, method: {method}, content: {content}"; - _logger.LogWarning($"{msg}\n(Error: {ex.Message})"); + _logger.LogError($"{msg}\n(Error: {ex.Message}\r\n{ex.InnerException})"); message.Content = msg; return false; } @@ -57,7 +56,7 @@ public class HandleHttpRequestFn : IFunctionCallback if (string.IsNullOrEmpty(url)) return null; using var client = _httpClientFactory.CreateClient(); - AddRequestHeaders(client); + PrepareRequestHeaders(client); var (uri, request) = BuildHttpRequest(url, method, content); var response = await client.SendAsync(request); @@ -69,15 +68,12 @@ public class HandleHttpRequestFn : IFunctionCallback return response; } - private void AddRequestHeaders(HttpClient client) + private void PrepareRequestHeaders(HttpClient client) { - client.DefaultRequestHeaders.Add("Authorization", $"{_context.HttpContext.Request.Headers["Authorization"]}"); - - var settings = _services.GetRequiredService(); - var origin = !string.IsNullOrEmpty(settings.Origin) ? settings.Origin : $"{_context.HttpContext.Request.Headers["Origin"]}"; - if (!string.IsNullOrEmpty(origin)) + var hooks = _services.GetServices(); + foreach (var hook in hooks) { - client.DefaultRequestHeaders.Add("Origin", origin); + hook.OnAddHttpHeaders(client.DefaultRequestHeaders); } } diff --git a/src/Plugins/BotSharp.Plugin.HttpHandler/Hooks/BasicHttpRequestHook.cs b/src/Plugins/BotSharp.Plugin.HttpHandler/Hooks/BasicHttpRequestHook.cs new file mode 100644 index 00000000..5de858f2 --- /dev/null +++ b/src/Plugins/BotSharp.Plugin.HttpHandler/Hooks/BasicHttpRequestHook.cs @@ -0,0 +1,40 @@ +using BotSharp.Abstraction.Http; +using Microsoft.AspNetCore.Http; +using System.Net.Http.Headers; + +namespace BotSharp.Plugin.HttpHandler.Hooks; + +public class BasicHttpRequestHook : IHttpRequestHook +{ + private readonly IServiceProvider _services; + private readonly IHttpContextAccessor _context; + + private const string AUTHORIZATION = "Authorization"; + private const string ORIGIN = "Origin"; + + public BasicHttpRequestHook( + IServiceProvider services, + IHttpContextAccessor context) + { + _services = services; + _context = context; + } + + public void OnAddHttpHeaders(HttpHeaders headers) + { + var settings = _services.GetRequiredService(); + + var auth = $"{_context.HttpContext.Request.Headers[AUTHORIZATION]}"; + if (!string.IsNullOrEmpty(auth)) + { + headers.Add(AUTHORIZATION, auth); + } + + var origin = $"{_context.HttpContext.Request.Headers[ORIGIN]}"; + origin = !string.IsNullOrEmpty(settings.Origin) ? settings.Origin : origin; + if (!string.IsNullOrEmpty(origin)) + { + headers.Add(ORIGIN, origin); + } + } +} diff --git a/src/Plugins/BotSharp.Plugin.HttpHandler/HttpHandlerPlugin.cs b/src/Plugins/BotSharp.Plugin.HttpHandler/HttpHandlerPlugin.cs index 9c8cab55..a3c85c0b 100644 --- a/src/Plugins/BotSharp.Plugin.HttpHandler/HttpHandlerPlugin.cs +++ b/src/Plugins/BotSharp.Plugin.HttpHandler/HttpHandlerPlugin.cs @@ -1,4 +1,5 @@ using BotSharp.Abstraction.Agents; +using BotSharp.Abstraction.Http; using BotSharp.Abstraction.Settings; using Microsoft.Extensions.Configuration; @@ -21,5 +22,6 @@ public class HttpHandlerPlugin : IBotSharpPlugin }); services.AddScoped(); + services.AddScoped(); } } diff --git a/src/Plugins/BotSharp.Plugin.MongoStorage/BotSharp.Plugin.MongoStorage.csproj b/src/Plugins/BotSharp.Plugin.MongoStorage/BotSharp.Plugin.MongoStorage.csproj index 88709238..5ac884e0 100644 --- a/src/Plugins/BotSharp.Plugin.MongoStorage/BotSharp.Plugin.MongoStorage.csproj +++ b/src/Plugins/BotSharp.Plugin.MongoStorage/BotSharp.Plugin.MongoStorage.csproj @@ -11,7 +11,7 @@ - + diff --git a/src/Plugins/BotSharp.Plugin.MongoStorage/Collections/AgentDocument.cs b/src/Plugins/BotSharp.Plugin.MongoStorage/Collections/AgentDocument.cs index 19945124..54c9eae7 100644 --- a/src/Plugins/BotSharp.Plugin.MongoStorage/Collections/AgentDocument.cs +++ b/src/Plugins/BotSharp.Plugin.MongoStorage/Collections/AgentDocument.cs @@ -21,6 +21,7 @@ public class AgentDocument : MongoBase public List KnowledgeBases { get; set; } public List Profiles { get; set; } public List RoutingRules { get; set; } + public List Rules { get; set; } public AgentLlmConfigMongoElement? LlmConfig { get; set; } public DateTime CreatedTime { get; set; } diff --git a/src/Plugins/BotSharp.Plugin.MongoStorage/Models/AgentRuleMongoElement.cs b/src/Plugins/BotSharp.Plugin.MongoStorage/Models/AgentRuleMongoElement.cs new file mode 100644 index 00000000..1b346768 --- /dev/null +++ b/src/Plugins/BotSharp.Plugin.MongoStorage/Models/AgentRuleMongoElement.cs @@ -0,0 +1,33 @@ +using BotSharp.Abstraction.Agents.Models; + +namespace BotSharp.Plugin.MongoStorage.Models; + +public class AgentRuleMongoElement +{ + public string Name { get; set; } + public bool Disabled { get; set; } + public string EventName { get; set; } + public string EntityType { get; set; } + + public static AgentRuleMongoElement ToMongoElement(AgentRule rule) + { + return new AgentRuleMongoElement + { + Name = rule.Name, + Disabled = rule.Disabled, + EventName = rule.EventName, + EntityType = rule.EntityType + }; + } + + public static AgentRule ToDomainElement(AgentRuleMongoElement rule) + { + return new AgentRule + { + Name = rule.Name, + Disabled = rule.Disabled, + EventName = rule.EventName, + EntityType = rule.EntityType + }; + } +} diff --git a/src/Plugins/BotSharp.Plugin.MongoStorage/MongoDbContext.cs b/src/Plugins/BotSharp.Plugin.MongoStorage/MongoDbContext.cs index 89eb18ad..82e9f0d4 100644 --- a/src/Plugins/BotSharp.Plugin.MongoStorage/MongoDbContext.cs +++ b/src/Plugins/BotSharp.Plugin.MongoStorage/MongoDbContext.cs @@ -36,7 +36,7 @@ public class MongoDbContext Key = x.Split("=")[0], Value = x.Split("=")[1] }).ToList(); - + var source = queries.FirstOrDefault(x => x.Key.IsEqualTo(DB_NAME_INDEX)); if (source != null) { @@ -168,4 +168,5 @@ public class MongoDbContext public IMongoCollection CrontabItems => Database.GetCollection($"{_collectionPrefix}_CronTabItems"); + } diff --git a/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.Agent.cs b/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.Agent.cs index 7f86d144..af615298 100644 --- a/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.Agent.cs +++ b/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.Agent.cs @@ -61,6 +61,9 @@ public partial class MongoRepository case AgentField.KnowledgeBase: UpdateAgentKnowledgeBases(agent.Id, agent.KnowledgeBases); break; + case AgentField.Rule: + UpdateAgentRules(agent.Id, agent.Rules); + break; case AgentField.MaxMessageCount: UpdateAgentMaxMessageCount(agent.Id, agent.MaxMessageCount); break; @@ -256,6 +259,20 @@ public partial class MongoRepository _dc.Agents.UpdateOne(filter, update); } + private void UpdateAgentRules(string agentId, List rules) + { + if (rules == null) return; + + var elements = rules?.Select(x => AgentRuleMongoElement.ToMongoElement(x))?.ToList() ?? []; + + var filter = Builders.Filter.Eq(x => x.Id, agentId); + var update = Builders.Update + .Set(x => x.Rules, elements) + .Set(x => x.UpdatedTime, DateTime.UtcNow); + + _dc.Agents.UpdateOne(filter, update); + } + private void UpdateAgentLlmConfig(string agentId, AgentLlmConfig? config) { var llmConfig = AgentLlmConfigMongoElement.ToMongoElement(config); @@ -297,12 +314,12 @@ public partial class MongoRepository .Set(x => x.Samples, agent.Samples) .Set(x => x.Utilities, agent.Utilities.Select(u => AgentUtilityMongoElement.ToMongoElement(u)).ToList()) .Set(x => x.KnowledgeBases, agent.KnowledgeBases.Select(u => AgentKnowledgeBaseMongoElement.ToMongoElement(u)).ToList()) + .Set(x => x.Rules, agent.Rules.Select(e => AgentRuleMongoElement.ToMongoElement(e)).ToList()) .Set(x => x.LlmConfig, AgentLlmConfigMongoElement.ToMongoElement(agent.LlmConfig)) .Set(x => x.IsPublic, agent.IsPublic) .Set(x => x.UpdatedTime, DateTime.UtcNow); var res = _dc.Agents.UpdateOne(filter, update); - Console.WriteLine(); } #endregion @@ -455,6 +472,7 @@ public partial class MongoRepository RoutingRules = x.RoutingRules?.Select(r => RoutingRuleMongoElement.ToMongoElement(r))?.ToList() ?? [], Utilities = x.Utilities?.Select(u => AgentUtilityMongoElement.ToMongoElement(u))?.ToList() ?? [], KnowledgeBases = x.KnowledgeBases?.Select(k => AgentKnowledgeBaseMongoElement.ToMongoElement(k))?.ToList() ?? [], + Rules = x.Rules?.Select(e => AgentRuleMongoElement.ToMongoElement(e))?.ToList() ?? [], CreatedTime = x.CreatedDateTime, UpdatedTime = x.UpdatedDateTime }).ToList(); @@ -546,7 +564,8 @@ public partial class MongoRepository Responses = agentDoc.Responses?.Select(r => AgentResponseMongoElement.ToDomainElement(r))?.ToList() ?? [], RoutingRules = agentDoc.RoutingRules?.Select(r => RoutingRuleMongoElement.ToDomainElement(agentDoc.Id, agentDoc.Name, r))?.ToList() ?? [], Utilities = agentDoc.Utilities?.Select(u => AgentUtilityMongoElement.ToDomainElement(u))?.ToList() ?? [], - KnowledgeBases = agentDoc.KnowledgeBases?.Select(x => AgentKnowledgeBaseMongoElement.ToDomainElement(x))?.ToList() ?? [] + KnowledgeBases = agentDoc.KnowledgeBases?.Select(x => AgentKnowledgeBaseMongoElement.ToDomainElement(x))?.ToList() ?? [], + Rules = agentDoc.Rules?.Select(e => AgentRuleMongoElement.ToDomainElement(e))?.ToList() ?? [] }; } } diff --git a/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.User.cs b/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.User.cs index 3c76e21d..29bd36be 100644 --- a/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.User.cs +++ b/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.User.cs @@ -112,6 +112,14 @@ public partial class MongoRepository _dc.Users.UpdateOne(filter, update); } + public void UpdateUserName(string userId, string userName) + { + var filter = Builders.Filter.Eq(x => x.Id, userId); + var update = Builders.Update + .Set(x => x.UserName, userName); + _dc.Users.UpdateOne(filter, update); + } + public void UpdateUserVerified(string userId) { var filter = Builders.Filter.Eq(x => x.Id, userId); @@ -151,7 +159,7 @@ public partial class MongoRepository var update = Builders.Update.Set(x => x.Phone, phone) .Set(x => x.UpdatedTime, DateTime.UtcNow) .Set(x => x.RegionCode, regionCode) - .Set(x => x.UserName, phone) + //.Set(x => x.UserName, phone) .Set(x => x.FirstName, phone); _dc.Users.UpdateOne(filter, update); } @@ -331,10 +339,10 @@ public partial class MongoRepository .FirstOrDefault(x => x.Id == userId || (x.ExternalId != null && x.ExternalId == userId)); if (user == null) return; var curDash = user.Dashboard ?? new Dashboard(); - curDash.ConversationList.Add(new DashboardConversation - { + curDash.ConversationList.Add(new DashboardConversation + { Id = Guid.NewGuid().ToString(), - ConversationId = conversationId + ConversationId = conversationId }); var filter = Builders.Filter.Eq(x => x.Id, userId); diff --git a/src/Plugins/BotSharp.Plugin.OpenAI/Providers/ProviderHelper.cs b/src/Plugins/BotSharp.Plugin.OpenAI/Providers/ProviderHelper.cs index b385b380..d3db3544 100644 --- a/src/Plugins/BotSharp.Plugin.OpenAI/Providers/ProviderHelper.cs +++ b/src/Plugins/BotSharp.Plugin.OpenAI/Providers/ProviderHelper.cs @@ -9,8 +9,9 @@ public class ProviderHelper { var settingsService = services.GetRequiredService(); var settings = settingsService.GetSetting(provider, model); - var client = new OpenAIClient(new ApiKeyCredential(settings.ApiKey)); - return client; + var options = !string.IsNullOrEmpty(settings.Endpoint) ? + new OpenAIClientOptions { Endpoint = new Uri(settings.Endpoint) } : null; + return new OpenAIClient(new ApiKeyCredential(settings.ApiKey), options); } public static List GetChatSamples(List lines) diff --git a/src/Plugins/BotSharp.Plugin.Selenium/BotSharp.Plugin.Selenium.csproj b/src/Plugins/BotSharp.Plugin.Selenium/BotSharp.Plugin.Selenium.csproj new file mode 100644 index 00000000..2a4e3e08 --- /dev/null +++ b/src/Plugins/BotSharp.Plugin.Selenium/BotSharp.Plugin.Selenium.csproj @@ -0,0 +1,22 @@ + + + + $(TargetFramework) + enable + $(LangVersion) + $(BotSharpVersion) + $(GeneratePackageOnBuild) + $(GenerateDocumentationFile) + $(SolutionDir)packages + + + + + + + + + + + + diff --git a/src/Plugins/BotSharp.Plugin.WebDriver/Drivers/SeleniumDriver/SeleniumInstance.cs b/src/Plugins/BotSharp.Plugin.Selenium/Drivers/SeleniumInstance.cs similarity index 97% rename from src/Plugins/BotSharp.Plugin.WebDriver/Drivers/SeleniumDriver/SeleniumInstance.cs rename to src/Plugins/BotSharp.Plugin.Selenium/Drivers/SeleniumInstance.cs index 5e1f5eaf..253ba487 100644 --- a/src/Plugins/BotSharp.Plugin.WebDriver/Drivers/SeleniumDriver/SeleniumInstance.cs +++ b/src/Plugins/BotSharp.Plugin.Selenium/Drivers/SeleniumInstance.cs @@ -1,7 +1,7 @@ using OpenQA.Selenium.Chrome; using System.IO; -namespace BotSharp.Plugin.WebDriver.Drivers.SeleniumDriver; +namespace BotSharp.Plugin.Selenium.Drivers; public class SeleniumInstance : IDisposable { diff --git a/src/Plugins/BotSharp.Plugin.WebDriver/Drivers/SeleniumDriver/SeleniumWebDriver.DoAction.cs b/src/Plugins/BotSharp.Plugin.Selenium/Drivers/SeleniumWebDriver.DoAction.cs similarity index 93% rename from src/Plugins/BotSharp.Plugin.WebDriver/Drivers/SeleniumDriver/SeleniumWebDriver.DoAction.cs rename to src/Plugins/BotSharp.Plugin.Selenium/Drivers/SeleniumWebDriver.DoAction.cs index bb9afd87..931e6736 100644 --- a/src/Plugins/BotSharp.Plugin.WebDriver/Drivers/SeleniumDriver/SeleniumWebDriver.DoAction.cs +++ b/src/Plugins/BotSharp.Plugin.Selenium/Drivers/SeleniumWebDriver.DoAction.cs @@ -1,7 +1,6 @@ -using OpenQA.Selenium; using OpenQA.Selenium.Interactions; -namespace BotSharp.Plugin.WebDriver.Drivers.SeleniumDriver; +namespace BotSharp.Plugin.Selenium.Drivers; public partial class SeleniumWebDriver { diff --git a/src/Plugins/BotSharp.Plugin.WebDriver/Drivers/SeleniumDriver/SeleniumWebDriver.EvaluateScript.cs b/src/Plugins/BotSharp.Plugin.Selenium/Drivers/SeleniumWebDriver.EvaluateScript.cs similarity index 86% rename from src/Plugins/BotSharp.Plugin.WebDriver/Drivers/SeleniumDriver/SeleniumWebDriver.EvaluateScript.cs rename to src/Plugins/BotSharp.Plugin.Selenium/Drivers/SeleniumWebDriver.EvaluateScript.cs index 9a59cb81..3fb6b39a 100644 --- a/src/Plugins/BotSharp.Plugin.WebDriver/Drivers/SeleniumDriver/SeleniumWebDriver.EvaluateScript.cs +++ b/src/Plugins/BotSharp.Plugin.Selenium/Drivers/SeleniumWebDriver.EvaluateScript.cs @@ -1,4 +1,4 @@ -namespace BotSharp.Plugin.WebDriver.Drivers.SeleniumDriver; +namespace BotSharp.Plugin.Selenium.Drivers; public partial class SeleniumWebDriver { diff --git a/src/Plugins/BotSharp.Plugin.WebDriver/Drivers/SeleniumDriver/SeleniumWebDriver.GetAttributeValue.cs b/src/Plugins/BotSharp.Plugin.Selenium/Drivers/SeleniumWebDriver.GetAttributeValue.cs similarity index 91% rename from src/Plugins/BotSharp.Plugin.WebDriver/Drivers/SeleniumDriver/SeleniumWebDriver.GetAttributeValue.cs rename to src/Plugins/BotSharp.Plugin.Selenium/Drivers/SeleniumWebDriver.GetAttributeValue.cs index 0402b2db..ff99d29d 100644 --- a/src/Plugins/BotSharp.Plugin.WebDriver/Drivers/SeleniumDriver/SeleniumWebDriver.GetAttributeValue.cs +++ b/src/Plugins/BotSharp.Plugin.Selenium/Drivers/SeleniumWebDriver.GetAttributeValue.cs @@ -1,4 +1,4 @@ -namespace BotSharp.Plugin.WebDriver.Drivers.SeleniumDriver; +namespace BotSharp.Plugin.Selenium.Drivers; public partial class SeleniumWebDriver { diff --git a/src/Plugins/BotSharp.Plugin.WebDriver/Drivers/SeleniumDriver/SeleniumWebDriver.GoToPage.cs b/src/Plugins/BotSharp.Plugin.Selenium/Drivers/SeleniumWebDriver.GoToPage.cs similarity index 92% rename from src/Plugins/BotSharp.Plugin.WebDriver/Drivers/SeleniumDriver/SeleniumWebDriver.GoToPage.cs rename to src/Plugins/BotSharp.Plugin.Selenium/Drivers/SeleniumWebDriver.GoToPage.cs index db7d36a3..15a71480 100644 --- a/src/Plugins/BotSharp.Plugin.WebDriver/Drivers/SeleniumDriver/SeleniumWebDriver.GoToPage.cs +++ b/src/Plugins/BotSharp.Plugin.Selenium/Drivers/SeleniumWebDriver.GoToPage.cs @@ -1,4 +1,4 @@ -namespace BotSharp.Plugin.WebDriver.Drivers.SeleniumDriver; +namespace BotSharp.Plugin.Selenium.Drivers; public partial class SeleniumWebDriver { diff --git a/src/Plugins/BotSharp.Plugin.WebDriver/Drivers/SeleniumDriver/SeleniumWebDriver.HttpRequest.cs b/src/Plugins/BotSharp.Plugin.Selenium/Drivers/SeleniumWebDriver.HttpRequest.cs similarity index 95% rename from src/Plugins/BotSharp.Plugin.WebDriver/Drivers/SeleniumDriver/SeleniumWebDriver.HttpRequest.cs rename to src/Plugins/BotSharp.Plugin.Selenium/Drivers/SeleniumWebDriver.HttpRequest.cs index c9940318..affeaf62 100644 --- a/src/Plugins/BotSharp.Plugin.WebDriver/Drivers/SeleniumDriver/SeleniumWebDriver.HttpRequest.cs +++ b/src/Plugins/BotSharp.Plugin.Selenium/Drivers/SeleniumWebDriver.HttpRequest.cs @@ -1,6 +1,6 @@ using System.Net.Http; -namespace BotSharp.Plugin.WebDriver.Drivers.SeleniumDriver; +namespace BotSharp.Plugin.Selenium.Drivers; public partial class SeleniumWebDriver { diff --git a/src/Plugins/BotSharp.Plugin.WebDriver/Drivers/SeleniumDriver/SeleniumWebDriver.LaunchBrowser.cs b/src/Plugins/BotSharp.Plugin.Selenium/Drivers/SeleniumWebDriver.LaunchBrowser.cs similarity index 85% rename from src/Plugins/BotSharp.Plugin.WebDriver/Drivers/SeleniumDriver/SeleniumWebDriver.LaunchBrowser.cs rename to src/Plugins/BotSharp.Plugin.Selenium/Drivers/SeleniumWebDriver.LaunchBrowser.cs index 4e8b6498..9c104a59 100644 --- a/src/Plugins/BotSharp.Plugin.WebDriver/Drivers/SeleniumDriver/SeleniumWebDriver.LaunchBrowser.cs +++ b/src/Plugins/BotSharp.Plugin.Selenium/Drivers/SeleniumWebDriver.LaunchBrowser.cs @@ -1,4 +1,4 @@ -namespace BotSharp.Plugin.WebDriver.Drivers.SeleniumDriver; +namespace BotSharp.Plugin.Selenium.Drivers; public partial class SeleniumWebDriver { diff --git a/src/Plugins/BotSharp.Plugin.WebDriver/Drivers/SeleniumDriver/SeleniumWebDriver.LocateElement.cs b/src/Plugins/BotSharp.Plugin.Selenium/Drivers/SeleniumWebDriver.LocateElement.cs similarity index 97% rename from src/Plugins/BotSharp.Plugin.WebDriver/Drivers/SeleniumDriver/SeleniumWebDriver.LocateElement.cs rename to src/Plugins/BotSharp.Plugin.Selenium/Drivers/SeleniumWebDriver.LocateElement.cs index 4d96b9a6..184106a5 100644 --- a/src/Plugins/BotSharp.Plugin.WebDriver/Drivers/SeleniumDriver/SeleniumWebDriver.LocateElement.cs +++ b/src/Plugins/BotSharp.Plugin.Selenium/Drivers/SeleniumWebDriver.LocateElement.cs @@ -1,7 +1,6 @@ -using OpenQA.Selenium; using System.Collections.ObjectModel; -namespace BotSharp.Plugin.WebDriver.Drivers.SeleniumDriver; +namespace BotSharp.Plugin.Selenium.Drivers; public partial class SeleniumWebDriver { diff --git a/src/Plugins/BotSharp.Plugin.WebDriver/Drivers/SeleniumDriver/SeleniumWebDriver.cs b/src/Plugins/BotSharp.Plugin.Selenium/Drivers/SeleniumWebDriver.cs similarity index 93% rename from src/Plugins/BotSharp.Plugin.WebDriver/Drivers/SeleniumDriver/SeleniumWebDriver.cs rename to src/Plugins/BotSharp.Plugin.Selenium/Drivers/SeleniumWebDriver.cs index 15dd0bc1..0e6dd0a3 100644 --- a/src/Plugins/BotSharp.Plugin.WebDriver/Drivers/SeleniumDriver/SeleniumWebDriver.cs +++ b/src/Plugins/BotSharp.Plugin.Selenium/Drivers/SeleniumWebDriver.cs @@ -1,4 +1,4 @@ -namespace BotSharp.Plugin.WebDriver.Drivers.SeleniumDriver; +namespace BotSharp.Plugin.Selenium.Drivers; public partial class SeleniumWebDriver : IWebBrowser { @@ -86,4 +86,9 @@ public partial class SeleniumWebDriver : IWebBrowser { throw new NotImplementedException(); } + + public Task PressKey(MessageInfo message, string key) + { + throw new NotImplementedException(); + } } diff --git a/src/Plugins/BotSharp.Plugin.Selenium/Using.cs b/src/Plugins/BotSharp.Plugin.Selenium/Using.cs new file mode 100644 index 00000000..abadf8fa --- /dev/null +++ b/src/Plugins/BotSharp.Plugin.Selenium/Using.cs @@ -0,0 +1,23 @@ +global using System; +global using System.Collections.Generic; +global using System.Text; +global using System.Threading.Tasks; +global using System.Text.Json; +global using System.Linq; +global using System.Text.RegularExpressions; +global using Microsoft.Extensions.Configuration; +global using Microsoft.Extensions.DependencyInjection; +global using Microsoft.Extensions.Logging; +global using BotSharp.Abstraction.Browsing.Enums; +global using BotSharp.Abstraction.Conversations; +global using BotSharp.Abstraction.Plugins; +global using BotSharp.Abstraction.Conversations.Models; +global using BotSharp.Abstraction.Functions; +global using BotSharp.Abstraction.Agents.Models; +global using BotSharp.Abstraction.Templating; +global using BotSharp.Abstraction.Agents; +global using BotSharp.Abstraction.Utilities; +global using BotSharp.Abstraction.Browsing.Models; +global using BotSharp.Abstraction.Browsing; + +global using OpenQA.Selenium; \ No newline at end of file diff --git a/src/Plugins/BotSharp.Plugin.SqlDriver/Hooks/SqlDriverPlanningHook.cs b/src/Plugins/BotSharp.Plugin.SqlDriver/Hooks/SqlDriverPlanningHook.cs index bb008319..231d793a 100644 --- a/src/Plugins/BotSharp.Plugin.SqlDriver/Hooks/SqlDriverPlanningHook.cs +++ b/src/Plugins/BotSharp.Plugin.SqlDriver/Hooks/SqlDriverPlanningHook.cs @@ -1,13 +1,9 @@ -using BotSharp.Abstraction.Agents.Enums; using BotSharp.Abstraction.Messaging.Enums; using BotSharp.Abstraction.Messaging.Models.RichContent.Template; using BotSharp.Abstraction.Messaging.Models.RichContent; using BotSharp.Abstraction.Messaging; using BotSharp.Abstraction.Planning; -using BotSharp.Abstraction.Routing; using BotSharp.Core.Infrastructures; -using System.Text.RegularExpressions; -using BotSharp.Plugin.SqlDriver.Interfaces; namespace BotSharp.Plugin.SqlDriver.Hooks; diff --git a/src/Plugins/BotSharp.Plugin.WebDriver/BotSharp.Plugin.WebDriver.csproj b/src/Plugins/BotSharp.Plugin.WebDriver/BotSharp.Plugin.WebDriver.csproj index 296e17c3..15c2868c 100644 --- a/src/Plugins/BotSharp.Plugin.WebDriver/BotSharp.Plugin.WebDriver.csproj +++ b/src/Plugins/BotSharp.Plugin.WebDriver/BotSharp.Plugin.WebDriver.csproj @@ -3,7 +3,7 @@ $(TargetFramework) enable - 12.0 + $(LangVersion) $(BotSharpVersion) $(GeneratePackageOnBuild) $(GenerateDocumentationFile) @@ -11,16 +11,7 @@ - - - - - - - - - - + diff --git a/src/Plugins/BotSharp.Plugin.WebDriver/Drivers/PlaywrightDriver/PlaywrightInstance.cs b/src/Plugins/BotSharp.Plugin.WebDriver/Drivers/PlaywrightDriver/PlaywrightInstance.cs index b5155cd5..95b88eee 100644 --- a/src/Plugins/BotSharp.Plugin.WebDriver/Drivers/PlaywrightDriver/PlaywrightInstance.cs +++ b/src/Plugins/BotSharp.Plugin.WebDriver/Drivers/PlaywrightDriver/PlaywrightInstance.cs @@ -45,28 +45,36 @@ public class PlaywrightInstance : IDisposable _playwright = await Playwright.CreateAsync(); } - string tempFolderPath = $"{Path.GetTempPath()}\\playwright\\{ctxId}"; - - _contexts[ctxId] = await _playwright.Chromium.LaunchPersistentContextAsync(tempFolderPath, new BrowserTypeLaunchPersistentContextOptions + if (!string.IsNullOrEmpty(args.RemoteHostUrl)) { - Headless = args.Headless, - Channel = "chrome", - ViewportSize = new ViewportSize + var browser = await _playwright.Chromium.ConnectOverCDPAsync(args.RemoteHostUrl); + _contexts[ctxId] = browser.Contexts[0]; + } + else + { + string userDataDir = args.UserDataDir ?? $"{Path.GetTempPath()}\\playwright\\{ctxId}"; + _contexts[ctxId] = await _playwright.Chromium.LaunchPersistentContextAsync(userDataDir, new BrowserTypeLaunchPersistentContextOptions { - Width = 1600, - Height = 900 - }, - IgnoreDefaultArgs = - [ - "--enable-automation", - ], - Args = - [ - "--disable-infobars", - "--test-type" - // "--start-maximized" - ] - }); + Headless = args.Headless, + Channel = "chrome", + ViewportSize = new ViewportSize + { + Width = 1600, + Height = 900 + }, + IgnoreDefaultArgs = + [ + "--enable-automation", + ], + Args = + [ + "--disable-infobars", + "--test-type" + // "--start-maximized" + ] + }); + } + _pages[ctxId] = new List(); _contexts[ctxId].Page += async (sender, page) => @@ -100,12 +108,7 @@ public class PlaywrightInstance : IDisposable return _contexts[ctxId]; } - public async Task NewPage(MessageInfo message, - bool enableResponseCallback = false, - bool responseInMemory = false, - List? responseContainer = null, - string[]? excludeResponseUrls = null, - string[]? includeResponseUrls = null) + public async Task NewPage(MessageInfo message, PageActionArgs args) { var context = await GetContext(message.ContextId); var page = await context.NewPageAsync(); @@ -115,70 +118,78 @@ public class PlaywrightInstance : IDisposable var js = @"Object.defineProperties(navigator, {webdriver:{get:()=>false}});"; await page.AddInitScriptAsync(js); - if (!enableResponseCallback) + if (!args.EnableResponseCallback) { return page; } page.Response += async (sender, e) => { - if (e.Status != 204 && - e.Headers.ContainsKey("content-type") && - (e.Request.ResourceType == "fetch" || e.Request.ResourceType == "xhr") && - (excludeResponseUrls == null || !excludeResponseUrls.Any(url => e.Url.ToLower().Contains(url))) && - (includeResponseUrls == null || includeResponseUrls.Any(url => e.Url.ToLower().Contains(url)))) - { - Serilog.Log.Information($"{e.Request.Method}: {e.Url}"); - - try - { - var result = new WebPageResponseData - { - Url = e.Url.ToLower(), - PostData = e.Request?.PostData ?? string.Empty, - ResponseInMemory = responseInMemory - }; - - if (e.Headers["content-type"].Contains("application/json")) - { - if (e.Status == 200 && e.Ok) - { - var json = await e.JsonAsync(); - result.ResponseData = JsonSerializer.Serialize(json); - } - } - else - { - var html = await e.TextAsync(); - result.ResponseData = html; - } - - if (responseContainer != null && responseInMemory) - { - responseContainer.Add(result); - } - - Serilog.Log.Warning($"Response status: {e.Status} {e.StatusText}, OK: {e.Ok}"); - var webPageResponseHooks = _services.GetServices(); - foreach (var hook in webPageResponseHooks) - { - hook.OnDataFetched(message, result); - } - } - catch (ObjectDisposedException ex) - { - Serilog.Log.Information(ex.Message); - } - catch (Exception ex) - { - Serilog.Log.Error($"{e.Url}\r\n" + ex.ToString()); - } - } + await HandleFetchResponse(e, message, args); }; return page; } + public async Task HandleFetchResponse(IResponse response, MessageInfo message, PageActionArgs args) + { + if (response.Status != 204 && + response.Headers.ContainsKey("content-type") && + (response.Request.ResourceType == "fetch" || response.Request.ResourceType == "xhr") && + (args.ExcludeResponseUrls == null || !args.ExcludeResponseUrls.Any(url => response.Url.ToLower().Contains(url))) && + (args.IncludeResponseUrls == null || args.IncludeResponseUrls.Any(url => response.Url.ToLower().Contains(url)))) + { + Serilog.Log.Information($"{response.Request.Method}: {response.Url}"); + + try + { + var result = new WebPageResponseData + { + Url = response.Url.ToLower(), + PostData = response.Request?.PostData ?? string.Empty, + ResponseInMemory = args.ResponseInMemory + }; + + var html = await response.TextAsync(); + if (response.Headers["content-type"].Contains("application/json")) + { + if (response.Status == 200 && response.Ok) + { + if (!string.IsNullOrWhiteSpace(html)) + { + var json = await response.JsonAsync(); + result.ResponseData = JsonSerializer.Serialize(json); + } + } + } + else + { + result.ResponseData = html; + } + + if (args.ResponseContainer != null && args.ResponseInMemory) + { + args.ResponseContainer.Add(result); + } + + Serilog.Log.Warning($"Response status: {response.Status} {response.StatusText}, OK: {response.Ok}"); + var webPageResponseHooks = _services.GetServices(); + foreach (var hook in webPageResponseHooks) + { + hook.OnDataFetched(message, result); + } + } + catch (ObjectDisposedException ex) + { + Serilog.Log.Information(ex.Message); + } + catch (Exception ex) + { + Serilog.Log.Error($"{response.Url}\r\n" + ex.ToString()); + } + } + } + /// /// Wait page and network until timeout in seconds /// diff --git a/src/Plugins/BotSharp.Plugin.WebDriver/Drivers/PlaywrightDriver/PlaywrightWebDriver.ActionOnElement.cs b/src/Plugins/BotSharp.Plugin.WebDriver/Drivers/PlaywrightDriver/PlaywrightWebDriver.ActionOnElement.cs index ab1a0731..7a5f4820 100644 --- a/src/Plugins/BotSharp.Plugin.WebDriver/Drivers/PlaywrightDriver/PlaywrightWebDriver.ActionOnElement.cs +++ b/src/Plugins/BotSharp.Plugin.WebDriver/Drivers/PlaywrightDriver/PlaywrightWebDriver.ActionOnElement.cs @@ -9,6 +9,7 @@ public partial class PlaywrightWebDriver if (result.IsSuccess) { await DoAction(message, action, result); + result.UrlAfterAction = _instance.GetPage(message.ContextId)?.Url; } return result; } 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 40fec84e..b851cd1a 100644 --- a/src/Plugins/BotSharp.Plugin.WebDriver/Drivers/PlaywrightDriver/PlaywrightWebDriver.DoAction.cs +++ b/src/Plugins/BotSharp.Plugin.WebDriver/Drivers/PlaywrightDriver/PlaywrightWebDriver.DoAction.cs @@ -113,6 +113,7 @@ public partial class PlaywrightWebDriver } // Release mouse button + await Task.Delay(1000); await mouse.UpAsync(); } else diff --git a/src/Plugins/BotSharp.Plugin.WebDriver/Drivers/PlaywrightDriver/PlaywrightWebDriver.GetAttributeValue.cs b/src/Plugins/BotSharp.Plugin.WebDriver/Drivers/PlaywrightDriver/PlaywrightWebDriver.GetAttributeValue.cs index 035bcf3b..ef266d08 100644 --- a/src/Plugins/BotSharp.Plugin.WebDriver/Drivers/PlaywrightDriver/PlaywrightWebDriver.GetAttributeValue.cs +++ b/src/Plugins/BotSharp.Plugin.WebDriver/Drivers/PlaywrightDriver/PlaywrightWebDriver.GetAttributeValue.cs @@ -19,4 +19,25 @@ public partial class PlaywrightWebDriver Body = value ?? string.Empty }; } + + public async Task SetAttributeValue(MessageInfo message, ElementLocatingArgs location) + { + var page = _instance.GetPage(message.ContextId); + ILocator locator = page.Locator(location.Selector); + var elementCount = await locator.CountAsync(); + + if (elementCount > 0) + { + foreach (var element in await locator.AllAsync()) + { + var script = $"element => element.{location.AttributeName} = '{location.AttributeValue}'"; + var result = await locator.EvaluateAsync(script); + } + } + + return new BrowserActionResult + { + IsSuccess = true + }; + } } 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 2f339ca6..7f0501cf 100644 --- a/src/Plugins/BotSharp.Plugin.WebDriver/Drivers/PlaywrightDriver/PlaywrightWebDriver.GoToPage.cs +++ b/src/Plugins/BotSharp.Plugin.WebDriver/Drivers/PlaywrightDriver/PlaywrightWebDriver.GoToPage.cs @@ -8,32 +8,41 @@ public partial class PlaywrightWebDriver var context = await _instance.GetContext(message.ContextId); try { - var page = await _instance.NewPage(message, enableResponseCallback: args.EnableResponseCallback, - responseInMemory: args.ResponseInMemory, - responseContainer: args.ResponseContainer, - excludeResponseUrls: args.ExcludeResponseUrls, - includeResponseUrls: args.IncludeResponseUrls); - - Serilog.Log.Information($"goto page: {args.Url}"); - - if (args.OpenNewTab && page != null && page.Url == "about:blank") + IPage? page = null; + if (!args.OpenNewTab) { - page = await _instance.NewPage(message, - enableResponseCallback: args.EnableResponseCallback, - responseInMemory: args.ResponseInMemory, - responseContainer: args.ResponseContainer, - excludeResponseUrls: args.ExcludeResponseUrls, - includeResponseUrls: args.IncludeResponseUrls); + page = _instance.Contexts[message.ContextId].Pages.LastOrDefault(); + + if (page != null) + { + await page.EvaluateAsync(@"() => { + window.open('', '_blank'); + }"); + + if (args.EnableResponseCallback) + { + page.Response += async (sender, e) => + { + await _instance.HandleFetchResponse(e, message, args); + }; + } + } + } + else + { + page = await _instance.NewPage(message, args); + + Serilog.Log.Information($"goto page: {args.Url}"); + + if (args.OpenNewTab && page != null && page.Url == "about:blank") + { + page = await _instance.NewPage(message, args); + } } if (page == null) { - page = await _instance.NewPage(message, - enableResponseCallback: args.EnableResponseCallback, - responseInMemory: args.ResponseInMemory, - responseContainer: args.ResponseContainer, - excludeResponseUrls: args.ExcludeResponseUrls, - includeResponseUrls: args.IncludeResponseUrls); + page = await _instance.NewPage(message, args); } // Active current tab diff --git a/src/Plugins/BotSharp.Plugin.WebDriver/Drivers/PlaywrightDriver/PlaywrightWebDriver.LocateElement.cs b/src/Plugins/BotSharp.Plugin.WebDriver/Drivers/PlaywrightDriver/PlaywrightWebDriver.LocateElement.cs index 8695fc5c..613d2b22 100644 --- a/src/Plugins/BotSharp.Plugin.WebDriver/Drivers/PlaywrightDriver/PlaywrightWebDriver.LocateElement.cs +++ b/src/Plugins/BotSharp.Plugin.WebDriver/Drivers/PlaywrightDriver/PlaywrightWebDriver.LocateElement.cs @@ -14,6 +14,13 @@ public partial class PlaywrightWebDriver { var result = new BrowserActionResult(); var page = _instance.GetPage(message.ContextId); + if (page == null) + { + return new BrowserActionResult + { + IsSuccess = false + }; + } ILocator locator = page.Locator("body"); int count = 0; diff --git a/src/Plugins/BotSharp.Plugin.WebDriver/Functions/OpenBrowserFn.cs b/src/Plugins/BotSharp.Plugin.WebDriver/Functions/OpenBrowserFn.cs index 6f4c3654..6a5a1ac1 100644 --- a/src/Plugins/BotSharp.Plugin.WebDriver/Functions/OpenBrowserFn.cs +++ b/src/Plugins/BotSharp.Plugin.WebDriver/Functions/OpenBrowserFn.cs @@ -1,5 +1,3 @@ -using SQLitePCL; - namespace BotSharp.Plugin.WebDriver.Functions; public class OpenBrowserFn : IFunctionCallback diff --git a/src/Plugins/BotSharp.Plugin.WebDriver/WebPageHelper.cs b/src/Plugins/BotSharp.Plugin.WebDriver/WebPageHelper.cs index 113403eb..aaf61aa4 100644 --- a/src/Plugins/BotSharp.Plugin.WebDriver/WebPageHelper.cs +++ b/src/Plugins/BotSharp.Plugin.WebDriver/WebPageHelper.cs @@ -1,10 +1,25 @@ using HtmlAgilityPack; -using System.Net.Http; namespace BotSharp.Plugin.WebDriver; public static class WebPageHelper { + public static HtmlNode GetElement(string html, string selector) + { + var htmlDoc = new HtmlDocument(); + htmlDoc.LoadHtml(html); + + return htmlDoc.DocumentNode.SelectSingleNode(selector); + } + + public static HtmlNodeCollection GetElements(string html, string selector) + { + var htmlDoc = new HtmlDocument(); + htmlDoc.LoadHtml(html); + + return htmlDoc.DocumentNode.SelectNodes(selector); + } + public static string RemoveElements(string html, string selector) { var htmlDoc = new HtmlDocument();