diff --git a/Directory.Build.props b/Directory.Build.props
index 068b6e13..4ca87cf3 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -2,7 +2,7 @@
net8.0
10.0
- 1.4.0
+ 1.5.1
true
false
diff --git a/src/Infrastructure/BotSharp.Abstraction/Agents/IAgentService.cs b/src/Infrastructure/BotSharp.Abstraction/Agents/IAgentService.cs
index db4db62a..b435a4d1 100644
--- a/src/Infrastructure/BotSharp.Abstraction/Agents/IAgentService.cs
+++ b/src/Infrastructure/BotSharp.Abstraction/Agents/IAgentService.cs
@@ -48,5 +48,7 @@ public interface IAgentService
string GetDataDir();
string GetAgentDataDir(string agentId);
+ List GetAgentsByUser(string userId);
+
PluginDef GetPlugin(string agentId);
}
diff --git a/src/Infrastructure/BotSharp.Abstraction/Browsing/IWebBrowser.cs b/src/Infrastructure/BotSharp.Abstraction/Browsing/IWebBrowser.cs
index 80dd6925..e2581ba9 100644
--- a/src/Infrastructure/BotSharp.Abstraction/Browsing/IWebBrowser.cs
+++ b/src/Infrastructure/BotSharp.Abstraction/Browsing/IWebBrowser.cs
@@ -4,7 +4,7 @@ namespace BotSharp.Abstraction.Browsing;
public interface IWebBrowser
{
- Task LaunchBrowser(string contextId, string? url, bool openIfNotExist = true);
+ Task LaunchBrowser(string contextId, string? url);
Task ScreenshotAsync(string contextId, string path);
Task ScrollPageAsync(BrowserActionParams actionParams);
@@ -24,6 +24,6 @@ public interface IWebBrowser
Task EvaluateScript(string contextId, string script);
Task CloseBrowser(string contextId);
Task CloseCurrentPage(string contextId);
- Task SendHttpRequest(string contextId, HttpRequestParams actionParams);
+ Task SendHttpRequest(MessageInfo message, HttpRequestParams actionParams);
Task GetAttributeValue(MessageInfo message, ElementLocatingArgs location);
}
diff --git a/src/Infrastructure/BotSharp.Abstraction/Browsing/Models/BrowserActionResult.cs b/src/Infrastructure/BotSharp.Abstraction/Browsing/Models/BrowserActionResult.cs
index a634cccd..b3576b90 100644
--- a/src/Infrastructure/BotSharp.Abstraction/Browsing/Models/BrowserActionResult.cs
+++ b/src/Infrastructure/BotSharp.Abstraction/Browsing/Models/BrowserActionResult.cs
@@ -8,4 +8,9 @@ public class BrowserActionResult
public string Selector { get; set; }
public string Body { get; set; }
public bool IsHighlighted { get; set; }
+
+ public override string ToString()
+ {
+ return $"{IsSuccess} - {Selector}";
+ }
}
diff --git a/src/Infrastructure/BotSharp.Abstraction/Browsing/Models/ElementActionArgs.cs b/src/Infrastructure/BotSharp.Abstraction/Browsing/Models/ElementActionArgs.cs
index 8b644b31..0d44066a 100644
--- a/src/Infrastructure/BotSharp.Abstraction/Browsing/Models/ElementActionArgs.cs
+++ b/src/Infrastructure/BotSharp.Abstraction/Browsing/Models/ElementActionArgs.cs
@@ -10,6 +10,8 @@ public class ElementActionArgs
public ElementPosition? Position { get; set; }
+ public string? PressKey { get; set; }
+
///
/// Required for deserialization
///
diff --git a/src/Infrastructure/BotSharp.Abstraction/Browsing/Models/ElementLocatingArgs.cs b/src/Infrastructure/BotSharp.Abstraction/Browsing/Models/ElementLocatingArgs.cs
index 6d6411d8..3b96c760 100644
--- a/src/Infrastructure/BotSharp.Abstraction/Browsing/Models/ElementLocatingArgs.cs
+++ b/src/Infrastructure/BotSharp.Abstraction/Browsing/Models/ElementLocatingArgs.cs
@@ -5,6 +5,9 @@ public class ElementLocatingArgs
[JsonPropertyName("match_rule")]
public string MatchRule { get; set; } = string.Empty;
+ [JsonPropertyName("tag")]
+ public string? Tag { get; set; } = null!;
+
[JsonPropertyName("text")]
public string? Text { get; set; }
@@ -20,6 +23,8 @@ public class ElementLocatingArgs
[JsonPropertyName("selector")]
public string? Selector { get; set; }
+ public bool Parent { get; set; }
+
public bool FailIfMultiple { get; set; }
///
diff --git a/src/Infrastructure/BotSharp.Abstraction/Browsing/Models/HttpRequestParams.cs b/src/Infrastructure/BotSharp.Abstraction/Browsing/Models/HttpRequestParams.cs
index 2b8cc052..e6d24a8e 100644
--- a/src/Infrastructure/BotSharp.Abstraction/Browsing/Models/HttpRequestParams.cs
+++ b/src/Infrastructure/BotSharp.Abstraction/Browsing/Models/HttpRequestParams.cs
@@ -18,7 +18,7 @@ public class HttpRequestParams
public HttpRequestParams(string url, HttpMethod method, string? payload = null)
{
- Method = HttpMethod.Get;
+ Method = method;
Url = url;
Payload = payload;
}
diff --git a/src/Infrastructure/BotSharp.Abstraction/Conversations/IConversationService.cs b/src/Infrastructure/BotSharp.Abstraction/Conversations/IConversationService.cs
index 9df6ae7f..c8b997ec 100644
--- a/src/Infrastructure/BotSharp.Abstraction/Conversations/IConversationService.cs
+++ b/src/Infrastructure/BotSharp.Abstraction/Conversations/IConversationService.cs
@@ -51,6 +51,9 @@ public interface IConversationService
///
/// Whether to reset all states
/// Append user init words
+ ///
///
- Task UpdateBreakpoint(bool resetStates = false, string? reason = null);
+ Task UpdateBreakpoint(bool resetStates = false, string? reason = null, params string[] excludedStates);
+
+ Task GetConversationSummary(IEnumerable conversationId);
}
diff --git a/src/Infrastructure/BotSharp.Abstraction/Files/IBotSharpFileService.cs b/src/Infrastructure/BotSharp.Abstraction/Files/IBotSharpFileService.cs
index 272abf0d..16197b9c 100644
--- a/src/Infrastructure/BotSharp.Abstraction/Files/IBotSharpFileService.cs
+++ b/src/Infrastructure/BotSharp.Abstraction/Files/IBotSharpFileService.cs
@@ -5,8 +5,11 @@ public interface IBotSharpFileService
string GetDirectory(string conversationId);
IEnumerable GetChatImages(string conversationId, List conversations, int offset = 2);
IEnumerable GetMessageFiles(string conversationId, IEnumerable messageIds, bool imageOnly = false);
- string? GetMessageFile(string conversationId, string messageId, string fileName);
- void SaveMessageFiles(string conversationId, string messageId, List files);
+ string GetMessageFile(string conversationId, string messageId, string fileName);
+ bool SaveMessageFiles(string conversationId, string messageId, List files);
+
+ string GetUserAvatar();
+ bool SaveUserAvatar(BotSharpFile file);
///
/// Delete files under messages
diff --git a/src/Infrastructure/BotSharp.Abstraction/Plugins/Models/PluginMenuDef.cs b/src/Infrastructure/BotSharp.Abstraction/Plugins/Models/PluginMenuDef.cs
index bd5a1d88..5f8a59b9 100644
--- a/src/Infrastructure/BotSharp.Abstraction/Plugins/Models/PluginMenuDef.cs
+++ b/src/Infrastructure/BotSharp.Abstraction/Plugins/Models/PluginMenuDef.cs
@@ -19,6 +19,9 @@ public class PluginMenuDef
[JsonIgnore]
public int Weight { get; set; }
+ [JsonIgnore]
+ public List? Roles { get; set; }
+
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public List? SubMenu { get; set; }
diff --git a/src/Infrastructure/BotSharp.Abstraction/Repositories/IBotSharpRepository.cs b/src/Infrastructure/BotSharp.Abstraction/Repositories/IBotSharpRepository.cs
index a451071e..6adcc8ad 100644
--- a/src/Infrastructure/BotSharp.Abstraction/Repositories/IBotSharpRepository.cs
+++ b/src/Infrastructure/BotSharp.Abstraction/Repositories/IBotSharpRepository.cs
@@ -17,10 +17,11 @@ public interface IBotSharpRepository
#endregion
#region User
- User? GetUserByEmail(string email);
- User? GetUserById(string id);
- User? GetUserByUserName(string userName);
- void CreateUser(User user);
+ User? GetUserByEmail(string email) => throw new NotImplementedException();
+ User? GetUserById(string id) => throw new NotImplementedException();
+ User? GetUserByUserName(string userName) => throw new NotImplementedException();
+ void CreateUser(User user) => throw new NotImplementedException();
+ void UpdateUserVerified(string userId) => throw new NotImplementedException();
#endregion
#region Agent
diff --git a/src/Infrastructure/BotSharp.Abstraction/Translation/Models/TranslationOutput.cs b/src/Infrastructure/BotSharp.Abstraction/Translation/Models/TranslationOutput.cs
index 52ad54ec..b15bfef4 100644
--- a/src/Infrastructure/BotSharp.Abstraction/Translation/Models/TranslationOutput.cs
+++ b/src/Infrastructure/BotSharp.Abstraction/Translation/Models/TranslationOutput.cs
@@ -9,5 +9,5 @@ public class TranslationOutput
public string OutputLanguage { get; set; } = LanguageType.ENGLISH;
[JsonPropertyName("texts")]
- public string[] Texts { get; set; } = Array.Empty();
+ public TranslationInput[] Texts { get; set; } = Array.Empty();
}
diff --git a/src/Infrastructure/BotSharp.Abstraction/Users/IAuthenticationHook.cs b/src/Infrastructure/BotSharp.Abstraction/Users/IAuthenticationHook.cs
index 33b8086f..0de10328 100644
--- a/src/Infrastructure/BotSharp.Abstraction/Users/IAuthenticationHook.cs
+++ b/src/Infrastructure/BotSharp.Abstraction/Users/IAuthenticationHook.cs
@@ -8,4 +8,5 @@ public interface IAuthenticationHook
Task Authenticate(string id, string password);
void AddClaims(List claims);
void BeforeSending(Token token);
+ Task UserCreated(User user);
}
diff --git a/src/Infrastructure/BotSharp.Abstraction/Users/IUserIdentity.cs b/src/Infrastructure/BotSharp.Abstraction/Users/IUserIdentity.cs
index a0998117..4fecb48c 100644
--- a/src/Infrastructure/BotSharp.Abstraction/Users/IUserIdentity.cs
+++ b/src/Infrastructure/BotSharp.Abstraction/Users/IUserIdentity.cs
@@ -8,4 +8,5 @@ public interface IUserIdentity
string FirstName { get; }
string LastName { get; }
string FullName { get; }
+ string? UserLanguage { get; }
}
diff --git a/src/Infrastructure/BotSharp.Abstraction/Users/IUserService.cs b/src/Infrastructure/BotSharp.Abstraction/Users/IUserService.cs
index debf68f4..4071790c 100644
--- a/src/Infrastructure/BotSharp.Abstraction/Users/IUserService.cs
+++ b/src/Infrastructure/BotSharp.Abstraction/Users/IUserService.cs
@@ -1,4 +1,5 @@
using BotSharp.Abstraction.Users.Models;
+using BotSharp.OpenAPI.ViewModels.Users;
namespace BotSharp.Abstraction.Users;
@@ -6,6 +7,9 @@ public interface IUserService
{
Task GetUser(string id);
Task CreateUser(User user);
+ Task ActiveUser(UserActivationModel model);
Task GetToken(string authorization);
Task GetMyProfile();
+ Task VerifyUserUnique(string userName);
+ Task VerifyEmailUnique(string email);
}
\ No newline at end of file
diff --git a/src/Infrastructure/BotSharp.Abstraction/Users/Models/User.cs b/src/Infrastructure/BotSharp.Abstraction/Users/Models/User.cs
index 7ca441fe..4e6ca265 100644
--- a/src/Infrastructure/BotSharp.Abstraction/Users/Models/User.cs
+++ b/src/Infrastructure/BotSharp.Abstraction/Users/Models/User.cs
@@ -14,6 +14,8 @@ public class User
public string Source { get; set; } = "internal";
public string? ExternalId { get; set; }
public string Role { get; set; } = UserRole.Client;
+ public string? VerificationCode { get; set; }
+ public bool Verified { get; set; }
public DateTime UpdatedTime { get; set; } = DateTime.UtcNow;
public DateTime CreatedTime { get; set; } = DateTime.UtcNow;
}
diff --git a/src/Infrastructure/BotSharp.Abstraction/Users/Models/UserActivationModel.cs b/src/Infrastructure/BotSharp.Abstraction/Users/Models/UserActivationModel.cs
new file mode 100644
index 00000000..904bd936
--- /dev/null
+++ b/src/Infrastructure/BotSharp.Abstraction/Users/Models/UserActivationModel.cs
@@ -0,0 +1,7 @@
+namespace BotSharp.OpenAPI.ViewModels.Users;
+
+public class UserActivationModel
+{
+ public string UserName { get; set; }
+ public string VerificationCode { get; set; }
+}
diff --git a/src/Infrastructure/BotSharp.Abstraction/Users/Settings/AccountSetting.cs b/src/Infrastructure/BotSharp.Abstraction/Users/Settings/AccountSetting.cs
new file mode 100644
index 00000000..06176ce6
--- /dev/null
+++ b/src/Infrastructure/BotSharp.Abstraction/Users/Settings/AccountSetting.cs
@@ -0,0 +1,9 @@
+namespace BotSharp.Abstraction.Users.Settings;
+
+public class AccountSetting
+{
+ ///
+ /// Whether to enable verification code to verify the authenticity of new users
+ ///
+ public bool NewUserVerification { get; set; }
+}
diff --git a/src/Infrastructure/BotSharp.Abstraction/Utilities/Pagination.cs b/src/Infrastructure/BotSharp.Abstraction/Utilities/Pagination.cs
index 92e3eacb..9ada5f63 100644
--- a/src/Infrastructure/BotSharp.Abstraction/Utilities/Pagination.cs
+++ b/src/Infrastructure/BotSharp.Abstraction/Utilities/Pagination.cs
@@ -13,17 +13,14 @@ public class Pagination
public int Size
{
- get
+ get
{
- if (_size <= 0) return 20;
- if (_size > 100) return 100;
-
- return _size;
- }
- set
+ return _size > 0 ? _size : 1;
+ }
+ set
{
_size = value;
- }
+ }
}
///
diff --git a/src/Infrastructure/BotSharp.Core/Agents/AgentPlugin.cs b/src/Infrastructure/BotSharp.Core/Agents/AgentPlugin.cs
index eb21fb31..83ce823c 100644
--- a/src/Infrastructure/BotSharp.Core/Agents/AgentPlugin.cs
+++ b/src/Infrastructure/BotSharp.Core/Agents/AgentPlugin.cs
@@ -1,6 +1,7 @@
using BotSharp.Abstraction.MLTasks;
using BotSharp.Abstraction.Plugins.Models;
using BotSharp.Abstraction.Settings;
+using BotSharp.Abstraction.Users.Enums;
using Microsoft.Extensions.Configuration;
namespace BotSharp.Core.Agents;
@@ -43,8 +44,8 @@ public class AgentPlugin : IBotSharpPlugin
{
SubMenu = new List
{
- new PluginMenuDef("Routing", link: "page/agent/router"), // icon: "bx bx-map-pin"
- new PluginMenuDef("Evaluating", link: "page/agent/evaluator"), // icon: "bx bx-task"
+ new PluginMenuDef("Routing", link: "page/agent/router") { Roles = new List { UserRole.Admin } }, // icon: "bx bx-map-pin"
+ new PluginMenuDef("Evaluating", link: "page/agent/evaluator") { Roles = new List { UserRole.Admin } }, // icon: "bx bx-task"
new PluginMenuDef("Agents", link: "page/agent"), // icon: "bx bx-bot"
}
});
diff --git a/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.CreateAgent.cs b/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.CreateAgent.cs
index ce6512af..ae429484 100644
--- a/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.CreateAgent.cs
+++ b/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.CreateAgent.cs
@@ -1,8 +1,4 @@
-using BotSharp.Abstraction.Agents.Models;
-using BotSharp.Abstraction.Functions.Models;
-using BotSharp.Abstraction.Repositories;
using BotSharp.Abstraction.Tasks.Models;
-using BotSharp.Abstraction.Users.Models;
using System.IO;
using System.Text.RegularExpressions;
@@ -26,32 +22,13 @@ public partial class AgentService
var dbSettings = _services.GetRequiredService();
var agentSettings = _services.GetRequiredService();
- var filePath = Path.Combine(dbSettings.FileRepository, agentSettings.DataDir);
- var foundAgent = FetchAgentFileByName(agent.Name, filePath);
-
- if (foundAgent != null)
- {
- agentRecord.SetId(foundAgent.Id)
- .SetName(foundAgent.Name)
- .SetDescription(foundAgent.Description)
- .SetIsPublic(foundAgent.IsPublic)
- .SetDisabled(foundAgent.Disabled)
- .SetAgentType(foundAgent.Type)
- .SetProfiles(foundAgent.Profiles)
- .SetRoutingRules(foundAgent.RoutingRules)
- .SetInstruction(foundAgent.Instruction)
- .SetTemplates(foundAgent.Templates)
- .SetFunctions(foundAgent.Functions)
- .SetResponses(foundAgent.Responses)
- .SetLlmConfig(foundAgent.LlmConfig);
- }
var user = _db.GetUserById(_user.Id);
var userAgentRecord = new UserAgent
{
Id = Guid.NewGuid().ToString(),
UserId = user.Id,
- AgentId = foundAgent?.Id ?? agentRecord.Id,
+ AgentId = agentRecord.Id,
Editable = false,
CreatedTime = DateTime.UtcNow,
UpdatedTime = DateTime.UtcNow
@@ -65,7 +42,7 @@ public partial class AgentService
Utilities.ClearCache();
- return agentRecord;
+ return await Task.FromResult(agentRecord);
}
private Agent FetchAgentFileByName(string agentName, string filePath)
diff --git a/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.DeleteAgent.cs b/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.DeleteAgent.cs
index 23111fd2..1fe5c6e1 100644
--- a/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.DeleteAgent.cs
+++ b/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.DeleteAgent.cs
@@ -1,9 +1,20 @@
+using BotSharp.Abstraction.Users.Enums;
+
namespace BotSharp.Core.Agents.Services;
public partial class AgentService
{
public async Task DeleteAgent(string id)
{
- throw new NotImplementedException();
+ var user = _db.GetUserById(_user.Id);
+ var agent = _db.GetAgentsByUser(_user.Id).FirstOrDefault(x => x.Id.IsEqualTo(id));
+
+ if (user?.Role != UserRole.Admin && agent == null)
+ {
+ return false;
+ }
+
+ var deleted = _db.DeleteAgent(id);
+ return await Task.FromResult(deleted);
}
}
diff --git a/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.UpdateAgent.cs b/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.UpdateAgent.cs
index 154d7398..af6cb65e 100644
--- a/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.UpdateAgent.cs
+++ b/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.UpdateAgent.cs
@@ -1,5 +1,6 @@
using BotSharp.Abstraction.Repositories.Enums;
using BotSharp.Abstraction.Routing.Models;
+using BotSharp.Abstraction.Users.Enums;
using System.IO;
namespace BotSharp.Core.Agents.Services;
@@ -8,6 +9,10 @@ public partial class AgentService
{
public async Task UpdateAgent(Agent agent, AgentField updateField)
{
+ var userService = _services.GetRequiredService();
+ var user = await userService.GetUser(_user.Id);
+ if (user?.Role != UserRole.Admin) return;
+
if (agent == null || string.IsNullOrEmpty(agent.Id)) return;
var record = _db.GetAgent(agent.Id);
diff --git a/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.cs b/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.cs
index bd009db0..da1b8cf1 100644
--- a/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.cs
+++ b/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.cs
@@ -47,4 +47,10 @@ public partial class AgentService : IAgentService
}
return dir;
}
+
+ public List GetAgentsByUser(string userId)
+ {
+ var agents = _db.GetAgentsByUser(userId);
+ return agents;
+ }
}
diff --git a/src/Infrastructure/BotSharp.Core/BotSharp.Core.csproj b/src/Infrastructure/BotSharp.Core/BotSharp.Core.csproj
index 0ddc52e8..10e21b3c 100644
--- a/src/Infrastructure/BotSharp.Core/BotSharp.Core.csproj
+++ b/src/Infrastructure/BotSharp.Core/BotSharp.Core.csproj
@@ -1,4 +1,4 @@
-
+
netstandard2.1
@@ -56,6 +56,7 @@
+
@@ -142,6 +143,9 @@
PreserveNewest
+
+ PreserveNewest
+
PreserveNewest
diff --git a/src/Infrastructure/BotSharp.Core/BotSharpCoreExtensions.cs b/src/Infrastructure/BotSharp.Core/BotSharpCoreExtensions.cs
index 51f3eb1d..2dd2dc7f 100644
--- a/src/Infrastructure/BotSharp.Core/BotSharpCoreExtensions.cs
+++ b/src/Infrastructure/BotSharp.Core/BotSharpCoreExtensions.cs
@@ -5,6 +5,7 @@ using BotSharp.Core.Plugins;
using BotSharp.Abstraction.Settings;
using BotSharp.Abstraction.Options;
using BotSharp.Abstraction.Messaging.JsonConverters;
+using BotSharp.Abstraction.Users.Settings;
namespace BotSharp.Core;
@@ -84,6 +85,10 @@ public static class BotSharpCoreExtensions
return settingService.Bind("PluginLoader");
});
+ var accountSettings = new AccountSetting();
+ config.Bind("Account", accountSettings);
+ services.AddScoped(x => accountSettings);
+
var loader = new PluginLoader(services, config, pluginSettings);
loader.Load(assembly =>
{
diff --git a/src/Infrastructure/BotSharp.Core/Conversations/ConversationPlugin.cs b/src/Infrastructure/BotSharp.Core/Conversations/ConversationPlugin.cs
index 6887622b..90eb3298 100644
--- a/src/Infrastructure/BotSharp.Core/Conversations/ConversationPlugin.cs
+++ b/src/Infrastructure/BotSharp.Core/Conversations/ConversationPlugin.cs
@@ -1,4 +1,5 @@
using BotSharp.Abstraction.Files;
+using BotSharp.Abstraction.Google.Settings;
using BotSharp.Abstraction.Instructs;
using BotSharp.Abstraction.Messaging;
using BotSharp.Abstraction.Plugins.Models;
@@ -34,6 +35,12 @@ public class ConversationPlugin : IBotSharpPlugin
return settingService.Bind("Conversation");
});
+ services.AddScoped(provider =>
+ {
+ var settingService = provider.GetRequiredService();
+ return settingService.Bind("GoogleApi");
+ });
+
services.AddScoped();
services.AddScoped();
services.AddScoped();
diff --git a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.Summary.cs b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.Summary.cs
new file mode 100644
index 00000000..533020f0
--- /dev/null
+++ b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.Summary.cs
@@ -0,0 +1,108 @@
+using BotSharp.Abstraction.MLTasks;
+using BotSharp.Abstraction.Templating;
+
+namespace BotSharp.Core.Conversations.Services;
+
+public partial class ConversationService
+{
+ public async Task GetConversationSummary(IEnumerable conversationIds)
+ {
+ if (conversationIds.IsNullOrEmpty()) return string.Empty;
+
+ var routing = _services.GetRequiredService();
+ var agentService = _services.GetRequiredService();
+
+ var contents = new List();
+ foreach ( var conversationId in conversationIds)
+ {
+ if (string.IsNullOrEmpty(conversationId)) continue;
+
+ var dialogs = _storage.GetDialogs(conversationId);
+
+ if (dialogs.IsNullOrEmpty()) continue;
+
+ var content = GetConversationContent(dialogs);
+ contents.Add(content);
+ }
+
+ var router = await agentService.LoadAgent(AIAssistant);
+ var prompt = GetPrompt(router, contents);
+ var summary = await Summarize(router, prompt);
+
+ return summary;
+ }
+
+ private string GetPrompt(Agent agent, List contents)
+ {
+ var template = agent.Templates.First(x => x.Name == "conversation.summary").Content;
+ var render = _services.GetRequiredService();
+
+ var texts = string.Empty;
+ for (int i = 0; i < contents.Count; i++)
+ {
+ texts += $"[Conversation {i+1}]\r\n{contents[i]}";
+ }
+
+ return render.Render(template, new Dictionary
+ {
+ { "texts", texts }
+ });
+ }
+
+ private async Task Summarize(Agent agent, string prompt)
+ {
+ var provider = "openai";
+ string? model;
+
+ var providerService = _services.GetRequiredService();
+ var modelSettings = providerService.GetProviderModels(provider);
+ var modelSetting = modelSettings.FirstOrDefault(x => x.Name.IsEqualTo("gpt4-turbo") || x.Name.IsEqualTo("gpt-4o"));
+
+ if (modelSetting != null)
+ {
+ model = modelSetting.Name;
+ }
+ else
+ {
+ provider = agent?.LlmConfig?.Provider;
+ model = agent?.LlmConfig?.Model;
+ if (provider == null || model == null)
+ {
+ var agentSettings = _services.GetRequiredService();
+ provider = agentSettings.LlmConfig.Provider;
+ model = agentSettings.LlmConfig.Model;
+ }
+ }
+
+ var chatCompletion = CompletionProvider.GetChatCompletion(_services, provider, model);
+ var response = await chatCompletion.GetChatCompletions(new Agent
+ {
+ Id = agent.Id,
+ Name = agent.Name,
+ Instruction = prompt
+ }, new List
+ {
+ new RoleDialogModel(AgentRole.User, "Please summarize the conversations.")
+ });
+
+ return response.Content;
+ }
+
+ private string GetConversationContent(List dialogs, int maxDialogCount = 50)
+ {
+ var conversation = "";
+
+ foreach (var dialog in dialogs.TakeLast(maxDialogCount))
+ {
+ var role = dialog.Role;
+ if (role != AgentRole.User)
+ {
+ role = AgentRole.Assistant;
+ }
+
+ conversation += $"{role}: {dialog.Payload ?? dialog.Content}\r\n";
+ }
+
+ return conversation + "\r\n";
+ }
+}
diff --git a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.UpdateBreakpoint.cs b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.UpdateBreakpoint.cs
index 7453a202..8f88f44f 100644
--- a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.UpdateBreakpoint.cs
+++ b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.UpdateBreakpoint.cs
@@ -4,7 +4,7 @@ namespace BotSharp.Core.Conversations.Services;
public partial class ConversationService : IConversationService
{
- public async Task UpdateBreakpoint(bool resetStates = false, string? reason = null)
+ public async Task UpdateBreakpoint(bool resetStates = false, string? reason = null, params string[] excludedStates)
{
var db = _services.GetRequiredService();
var routingCtx = _services.GetRequiredService();
@@ -22,7 +22,13 @@ public partial class ConversationService : IConversationService
{
var states = _services.GetRequiredService();
// keep language state
- states.CleanStates(StateConst.LANGUAGE);
+ if (excludedStates == null) excludedStates = new string[] { };
+ if (!excludedStates.Contains(StateConst.LANGUAGE))
+ {
+ excludedStates = excludedStates.Append(StateConst.LANGUAGE).ToArray();
+ }
+
+ states.CleanStates(excludedStates);
}
var hooks = _services.GetServices()
diff --git a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.cs b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.cs
index 4de36b9d..8e113226 100644
--- a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.cs
+++ b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.cs
@@ -12,6 +12,8 @@ public partial class ConversationService : IConversationService
private readonly IConversationStorage _storage;
private readonly IConversationStateService _state;
private string _conversationId;
+ private const string AIAssistant = "01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a";
+
public string ConversationId => _conversationId;
public IConversationStateService States => _state;
diff --git a/src/Infrastructure/BotSharp.Core/Files/BotSharpFileService.Conversation.cs b/src/Infrastructure/BotSharp.Core/Files/BotSharpFileService.Conversation.cs
new file mode 100644
index 00000000..f12ecb60
--- /dev/null
+++ b/src/Infrastructure/BotSharp.Core/Files/BotSharpFileService.Conversation.cs
@@ -0,0 +1,188 @@
+using Microsoft.AspNetCore.StaticFiles;
+using System.IO;
+using System.Threading;
+
+namespace BotSharp.Core.Files;
+
+public partial class BotSharpFileService
+{
+ public IEnumerable GetChatImages(string conversationId, List conversations, int offset = 1)
+ {
+ var files = new List();
+ if (string.IsNullOrEmpty(conversationId) || conversations.IsNullOrEmpty())
+ {
+ return files;
+ }
+
+ if (offset <= 0)
+ {
+ offset = MIN_OFFSET;
+ }
+ else if (offset > MAX_OFFSET)
+ {
+ offset = MAX_OFFSET;
+ }
+
+ var messageIds = conversations.Select(x => x.MessageId).Distinct().TakeLast(offset).ToList();
+ files = GetMessageFiles(conversationId, messageIds, imageOnly: true).ToList();
+ return files;
+ }
+
+ public IEnumerable GetMessageFiles(string conversationId, IEnumerable messageIds, bool imageOnly = false)
+ {
+ var files = new List();
+ if (messageIds.IsNullOrEmpty()) return files;
+
+ foreach (var messageId in messageIds)
+ {
+ var dir = GetConversationFileDirectory(conversationId, messageId);
+ if (!ExistDirectory(dir))
+ {
+ continue;
+ }
+
+ foreach (var file in Directory.GetFiles(dir))
+ {
+ var contentType = GetFileContentType(file);
+ if (imageOnly && !_allowedTypes.Contains(contentType))
+ {
+ continue;
+ }
+
+ var fileName = Path.GetFileNameWithoutExtension(file);
+ var extension = Path.GetExtension(file);
+ var fileType = extension.Substring(1);
+
+ var model = new MessageFileModel()
+ {
+ MessageId = messageId,
+ FileUrl = $"/conversation/{conversationId}/message/{messageId}/file/{fileName}",
+ FileStorageUrl = file,
+ FileName = fileName,
+ FileType = fileType,
+ ContentType = contentType
+ };
+ files.Add(model);
+ }
+ }
+
+ return files;
+ }
+
+ public string GetMessageFile(string conversationId, string messageId, string fileName)
+ {
+ var dir = GetConversationFileDirectory(conversationId, messageId);
+ if (!ExistDirectory(dir))
+ {
+ return string.Empty;
+ }
+
+ var found = Directory.GetFiles(dir).FirstOrDefault(f => Path.GetFileNameWithoutExtension(f).IsEqualTo(fileName));
+ return found;
+ }
+
+ public bool SaveMessageFiles(string conversationId, string messageId, List files)
+ {
+ if (files.IsNullOrEmpty()) return false;
+
+ var dir = GetConversationFileDirectory(conversationId, messageId, createNewDir: true);
+ if (!ExistDirectory(dir)) return false;
+
+ try
+ {
+ for (int i = 0; i < files.Count; i++)
+ {
+ var file = files[i];
+ if (string.IsNullOrEmpty(file.FileData))
+ {
+ continue;
+ }
+
+ var (_, bytes) = GetFileInfoFromData(file.FileData);
+ var fileType = Path.GetExtension(file.FileName);
+ var fileName = $"{i + 1}{fileType}";
+ Thread.Sleep(100);
+ File.WriteAllBytes(Path.Combine(dir, fileName), bytes);
+ }
+ return true;
+ }
+ catch (Exception ex)
+ {
+ _logger.LogWarning($"Error when saving conversation files: {ex.Message}");
+ return false;
+ }
+ }
+
+
+
+ public bool DeleteMessageFiles(string conversationId, IEnumerable messageIds, string targetMessageId, string? newMessageId = null)
+ {
+ if (string.IsNullOrEmpty(conversationId) || messageIds == null) return false;
+
+ if (!string.IsNullOrEmpty(targetMessageId) && !string.IsNullOrEmpty(newMessageId))
+ {
+ var prevDir = GetConversationFileDirectory(conversationId, targetMessageId);
+ var newDir = Path.Combine(_baseDir, CONVERSATION_FOLDER, conversationId, FILE_FOLDER, newMessageId);
+
+ if (ExistDirectory(prevDir))
+ {
+ if (ExistDirectory(newDir))
+ {
+ Directory.Delete(newDir, true);
+ }
+
+ Directory.Move(prevDir, newDir);
+ }
+ }
+
+ foreach (var messageId in messageIds)
+ {
+ var dir = GetConversationFileDirectory(conversationId, messageId);
+ if (string.IsNullOrEmpty(dir)) continue;
+
+ Thread.Sleep(100);
+ Directory.Delete(dir, true);
+ }
+
+ return true;
+ }
+
+ public bool DeleteConversationFiles(IEnumerable conversationIds)
+ {
+ if (conversationIds.IsNullOrEmpty()) return false;
+
+ foreach (var conversationId in conversationIds)
+ {
+ var convDir = FindConversationDirectory(conversationId);
+ if (!ExistDirectory(convDir)) continue;
+
+ Directory.Delete(convDir, true);
+ }
+ return true;
+ }
+
+ #region Private methods
+ private string GetConversationFileDirectory(string? conversationId, string? messageId, bool createNewDir = false)
+ {
+ if (string.IsNullOrEmpty(conversationId) || string.IsNullOrEmpty(messageId))
+ {
+ return string.Empty;
+ }
+
+ var dir = Path.Combine(_baseDir, CONVERSATION_FOLDER, conversationId, FILE_FOLDER, messageId);
+ if (!Directory.Exists(dir) && createNewDir)
+ {
+ Directory.CreateDirectory(dir);
+ }
+ return dir;
+ }
+
+ private string? FindConversationDirectory(string conversationId)
+ {
+ if (string.IsNullOrEmpty(conversationId)) return null;
+
+ var dir = Path.Combine(_baseDir, CONVERSATION_FOLDER, conversationId);
+ return dir;
+ }
+ #endregion
+}
diff --git a/src/Infrastructure/BotSharp.Core/Files/BotSharpFileService.User.cs b/src/Infrastructure/BotSharp.Core/Files/BotSharpFileService.User.cs
new file mode 100644
index 00000000..b6a87993
--- /dev/null
+++ b/src/Infrastructure/BotSharp.Core/Files/BotSharpFileService.User.cs
@@ -0,0 +1,65 @@
+using System.IO;
+
+namespace BotSharp.Core.Files;
+
+public partial class BotSharpFileService
+{
+ public string GetUserAvatar()
+ {
+ var db = _services.GetRequiredService();
+ var user = db.GetUserById(_user.Id);
+ var dir = GetUserAvatarDir(user?.Id);
+
+ if (!ExistDirectory(dir)) return string.Empty;
+
+ var found = Directory.GetFiles(dir).FirstOrDefault() ?? string.Empty;
+ return found;
+ }
+
+ public bool SaveUserAvatar(BotSharpFile file)
+ {
+ if (file == null || string.IsNullOrEmpty(file.FileData)) return false;
+
+ try
+ {
+ var db = _services.GetRequiredService();
+ var user = db.GetUserById(_user.Id);
+ var dir = GetUserAvatarDir(user?.Id);
+
+ if (string.IsNullOrEmpty(dir)) return false;
+
+ if (Directory.Exists(dir))
+ {
+ Directory.Delete(dir, true);
+ }
+
+ dir = GetUserAvatarDir(user?.Id, createNewDir: true);
+ var (_, bytes) = GetFileInfoFromData(file.FileData);
+ File.WriteAllBytes(Path.Combine(dir, file.FileName), bytes);
+ return true;
+ }
+ catch (Exception ex)
+ {
+ _logger.LogWarning($"Error when saving user avatar: {ex.Message}");
+ return false;
+ }
+ }
+
+
+ #region Private methods
+ private string GetUserAvatarDir(string? userId, bool createNewDir = false)
+ {
+ if (string.IsNullOrEmpty(userId))
+ {
+ return string.Empty;
+ }
+
+ var dir = Path.Combine(_baseDir, USERS_FOLDER, userId, USER_AVATAR_FOLDER);
+ if (!Directory.Exists(dir) && createNewDir)
+ {
+ Directory.CreateDirectory(dir);
+ }
+ return dir;
+ }
+ #endregion
+}
diff --git a/src/Infrastructure/BotSharp.Core/Files/BotSharpFileService.cs b/src/Infrastructure/BotSharp.Core/Files/BotSharpFileService.cs
index d7e961be..76d26dbc 100644
--- a/src/Infrastructure/BotSharp.Core/Files/BotSharpFileService.cs
+++ b/src/Infrastructure/BotSharp.Core/Files/BotSharpFileService.cs
@@ -1,28 +1,35 @@
using Microsoft.AspNetCore.StaticFiles;
+using System;
using System.IO;
using System.Threading;
namespace BotSharp.Core.Files;
-public class BotSharpFileService : IBotSharpFileService
+public partial class BotSharpFileService : IBotSharpFileService
{
private readonly BotSharpDatabaseSettings _dbSettings;
private readonly IServiceProvider _services;
+ private readonly IUserIdentity _user;
private readonly ILogger _logger;
private readonly string _baseDir;
private readonly IEnumerable _allowedTypes = new List { "image/png", "image/jpeg" };
private const string CONVERSATION_FOLDER = "conversations";
private const string FILE_FOLDER = "files";
+ private const string USERS_FOLDER = "users";
+ private const string USER_AVATAR_FOLDER = "avatar";
+
private const int MIN_OFFSET = 1;
private const int MAX_OFFSET = 5;
public BotSharpFileService(
BotSharpDatabaseSettings dbSettings,
+ IUserIdentity user,
ILogger logger,
IServiceProvider services)
{
_dbSettings = dbSettings;
+ _user = user;
_logger = logger;
_services = services;
_baseDir = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, dbSettings.FileRepository);
@@ -38,157 +45,6 @@ public class BotSharpFileService : IBotSharpFileService
return dir;
}
- public IEnumerable GetChatImages(string conversationId, List conversations, int offset = 2)
- {
- var files = new List();
- if (string.IsNullOrEmpty(conversationId) || conversations.IsNullOrEmpty())
- {
- return files;
- }
-
- if (offset <= 0)
- {
- offset = MIN_OFFSET;
- }
- else if (offset > MAX_OFFSET)
- {
- offset = MAX_OFFSET;
- }
-
- var messageIds = conversations.Select(x => x.MessageId).Distinct().TakeLast(offset).ToList();
- files = GetMessageFiles(conversationId, messageIds, imageOnly: true).ToList();
- return files;
- }
-
- public IEnumerable GetMessageFiles(string conversationId, IEnumerable messageIds, bool imageOnly = false)
- {
- var files = new List();
- if (messageIds.IsNullOrEmpty()) return files;
-
- foreach (var messageId in messageIds)
- {
- var dir = GetConversationFileDirectory(conversationId, messageId);
- if (string.IsNullOrEmpty(dir))
- {
- continue;
- }
-
- foreach (var file in Directory.GetFiles(dir))
- {
- var contentType = GetFileContentType(file);
- if (imageOnly && !_allowedTypes.Contains(contentType))
- {
- continue;
- }
-
- var fileName = Path.GetFileNameWithoutExtension(file);
- var extension = Path.GetExtension(file);
- var fileType = extension.Substring(1);
-
- var model = new MessageFileModel()
- {
- MessageId = messageId,
- FileUrl = $"/conversation/{conversationId}/message/{messageId}/file/{fileName}",
- FileStorageUrl = file,
- FileName = fileName,
- FileType = fileType,
- ContentType = contentType
- };
- files.Add(model);
- }
- }
-
- return files;
- }
-
- public string? GetMessageFile(string conversationId, string messageId, string fileName)
- {
- var dir = GetConversationFileDirectory(conversationId, messageId);
- if (string.IsNullOrEmpty(dir))
- {
- return null;
- }
-
- var found = Directory.GetFiles(dir).FirstOrDefault(f => Path.GetFileNameWithoutExtension(f).IsEqualTo(fileName));
- return found;
- }
-
- public void SaveMessageFiles(string conversationId, string messageId, List files)
- {
- if (files.IsNullOrEmpty()) return;
-
- var dir = GetConversationFileDirectory(conversationId, messageId, createNewDir: true);
- if (string.IsNullOrEmpty(dir)) return;
-
- try
- {
- for (int i = 0; i < files.Count; i++)
- {
- var file = files[i];
- if (string.IsNullOrEmpty(file.FileData))
- {
- continue;
- }
-
- var (_, bytes) = GetFileInfoFromData(file.FileData);
- var fileType = Path.GetExtension(file.FileName);
- var fileName = $"{i + 1}{fileType}";
- Thread.Sleep(100);
- File.WriteAllBytes(Path.Combine(dir, fileName), bytes);
- }
- }
- catch (Exception ex)
- {
- _logger.LogError($"Error when saving conversation files: {ex.Message}");
- }
- }
-
- public bool DeleteMessageFiles(string conversationId, IEnumerable messageIds, string targetMessageId, string? newMessageId = null)
- {
- if (string.IsNullOrEmpty(conversationId) || messageIds == null) return false;
-
- if (!string.IsNullOrEmpty(targetMessageId) && !string.IsNullOrEmpty(newMessageId))
- {
- var prevDir = GetConversationFileDirectory(conversationId, targetMessageId);
- var newDir = Path.Combine(_baseDir, CONVERSATION_FOLDER, conversationId, FILE_FOLDER, newMessageId);
-
- if (Directory.Exists(prevDir))
- {
- if (Directory.Exists(newDir))
- {
- Directory.Delete(newDir, true);
- }
-
- Directory.Move(prevDir, newDir);
- }
- }
-
- foreach ( var messageId in messageIds)
- {
- var dir = GetConversationFileDirectory(conversationId, messageId);
- if (string.IsNullOrEmpty(dir)) continue;
-
- Thread.Sleep(100);
- Directory.Delete(dir, true);
- }
-
- return true;
- }
-
- public bool DeleteConversationFiles(IEnumerable conversationIds)
- {
- if (conversationIds.IsNullOrEmpty()) return false;
-
- foreach (var conversationId in conversationIds)
- {
- var convDir = FindConversationDirectory(conversationId);
- if (string.IsNullOrEmpty(convDir)) continue;
-
- Directory.Delete(convDir, true);
- }
- return true;
- }
-
public (string, byte[]) GetFileInfoFromData(string data)
{
if (string.IsNullOrEmpty(data))
@@ -207,38 +63,6 @@ public class BotSharpFileService : IBotSharpFileService
}
#region Private methods
- private string GetConversationFileDirectory(string? conversationId, string? messageId, bool createNewDir = false)
- {
- if (string.IsNullOrEmpty(conversationId) || string.IsNullOrEmpty(messageId))
- {
- return string.Empty;
- }
-
- var dir = Path.Combine(_baseDir, CONVERSATION_FOLDER, conversationId, FILE_FOLDER, messageId);
- if (!Directory.Exists(dir))
- {
- if (createNewDir)
- {
- Directory.CreateDirectory(dir);
- }
- else
- {
- return string.Empty;
- }
- }
- return dir;
- }
-
- private string? FindConversationDirectory(string conversationId)
- {
- if (string.IsNullOrEmpty(conversationId)) return null;
-
- var dir = Path.Combine(_baseDir, CONVERSATION_FOLDER, conversationId);
- if (!Directory.Exists(dir)) return null;
-
- return dir;
- }
-
private string GetFileContentType(string filePath)
{
string contentType;
@@ -250,5 +74,10 @@ public class BotSharpFileService : IBotSharpFileService
return contentType;
}
+
+ private bool ExistDirectory(string? dir)
+ {
+ return !string.IsNullOrEmpty(dir) && Directory.Exists(dir);
+ }
#endregion
}
diff --git a/src/Infrastructure/BotSharp.Core/Plugins/PluginLoader.cs b/src/Infrastructure/BotSharp.Core/Plugins/PluginLoader.cs
index f6de19ac..1b883657 100644
--- a/src/Infrastructure/BotSharp.Core/Plugins/PluginLoader.cs
+++ b/src/Infrastructure/BotSharp.Core/Plugins/PluginLoader.cs
@@ -269,4 +269,20 @@ public class PluginLoader
}
});
}
+
+ public List GetPluginMenuByRoles(List plugins, string userRole)
+ {
+ if (plugins.IsNullOrEmpty()) return plugins;
+
+ var filtered = new List();
+ foreach (var plugin in plugins)
+ {
+ if (plugin.Roles.IsNullOrEmpty() || plugin.Roles.Contains(userRole))
+ {
+ plugin.SubMenu = GetPluginMenuByRoles(plugin.SubMenu, userRole);
+ filtered.Add(plugin);
+ }
+ }
+ return filtered;
+ }
}
diff --git a/src/Infrastructure/BotSharp.Core/Repository/BotSharpDbContext.cs b/src/Infrastructure/BotSharp.Core/Repository/BotSharpDbContext.cs
index 28aac06b..6bba6c2a 100644
--- a/src/Infrastructure/BotSharp.Core/Repository/BotSharpDbContext.cs
+++ b/src/Infrastructure/BotSharp.Core/Repository/BotSharpDbContext.cs
@@ -1,6 +1,5 @@
using BotSharp.Abstraction.Loggers.Models;
using BotSharp.Abstraction.Plugins.Models;
-using BotSharp.Abstraction.Repositories.Models;
using BotSharp.Abstraction.Tasks.Models;
using BotSharp.Abstraction.Users.Models;
using Microsoft.EntityFrameworkCore.Infrastructure;
@@ -176,20 +175,6 @@ public class BotSharpDbContext : Database, IBotSharpRepository
=> throw new NotImplementedException();
#endregion
- #region User
- public User? GetUserByEmail(string email)
- => throw new NotImplementedException();
-
- public User? GetUserById(string id)
- => throw new NotImplementedException();
-
- public User? GetUserByUserName(string userName)
- => throw new NotImplementedException();
-
- public void CreateUser(User user)
- => throw new NotImplementedException();
- #endregion
-
#region Execution Log
public void AddExecutionLogs(string conversationId, List logs)
{
diff --git a/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Agent.cs b/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Agent.cs
index a46669a5..b7141a5b 100644
--- a/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Agent.cs
+++ b/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Agent.cs
@@ -436,7 +436,40 @@ namespace BotSharp.Core.Repository
public bool DeleteAgent(string agentId)
{
- return false;
+ if (string.IsNullOrEmpty(agentId)) return false;
+
+ try
+ {
+ var agentDir = GetAgentDataDir(agentId);
+ if (string.IsNullOrEmpty(agentDir)) return false;
+
+ // Delete agent user relationships
+ var usersDir = Path.Combine(_dbSettings.FileRepository, "users");
+ if (Directory.Exists(usersDir))
+ {
+ foreach (var userDir in Directory.GetDirectories(usersDir))
+ {
+ var userAgentFile = Directory.GetFiles(userDir).FirstOrDefault(x => Path.GetFileName(x) == USER_AGENT_FILE);
+ if (string.IsNullOrEmpty(userAgentFile)) continue;
+
+ var text = File.ReadAllText(userAgentFile);
+ var userAgents = JsonSerializer.Deserialize>(text, _options);
+ if (userAgents.IsNullOrEmpty()) continue;
+
+ userAgents = userAgents.Where(x => x.AgentId != agentId).ToList();
+ File.WriteAllText(userAgentFile, JsonSerializer.Serialize(userAgents, _options));
+ }
+ }
+
+ // Delete agent folder
+ Directory.Delete(agentDir, true);
+
+ return true;
+ }
+ catch
+ {
+ return false;
+ }
}
}
}
diff --git a/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.User.cs b/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.User.cs
index bc992a08..e7566002 100644
--- a/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.User.cs
+++ b/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.User.cs
@@ -1,3 +1,4 @@
+using BotSharp.Abstraction.Users.Enums;
using BotSharp.Abstraction.Users.Models;
using System.IO;
@@ -32,4 +33,13 @@ public partial class FileRepository
var path = Path.Combine(dir, "user.json");
File.WriteAllText(path, JsonSerializer.Serialize(user, _options));
}
+
+ public void UpdateUserVerified(string userId)
+ {
+ var user = GetUserById(userId);
+ user.Verified = true;
+ var dir = Path.Combine(_dbSettings.FileRepository, "users", user.Id);
+ var path = Path.Combine(dir, "user.json");
+ File.WriteAllText(path, JsonSerializer.Serialize(user, _options));
+ }
}
diff --git a/src/Infrastructure/BotSharp.Core/Routing/RoutingContext.cs b/src/Infrastructure/BotSharp.Core/Routing/RoutingContext.cs
index 6b126a37..31caa932 100644
--- a/src/Infrastructure/BotSharp.Core/Routing/RoutingContext.cs
+++ b/src/Infrastructure/BotSharp.Core/Routing/RoutingContext.cs
@@ -118,6 +118,7 @@ public class RoutingContext : IRoutingContext
var message = new RoleDialogModel(AgentRole.User, $"Try to route to agent {agent.Name}")
{
+ CurrentAgentId = currentAgentId,
FunctionName = "route_to_agent",
FunctionArgs = JsonSerializer.Serialize(new FunctionCallFromLlm
{
diff --git a/src/Infrastructure/BotSharp.Core/Tasks/TaskPlugin.cs b/src/Infrastructure/BotSharp.Core/Tasks/TaskPlugin.cs
index ddf709ec..27c55ab5 100644
--- a/src/Infrastructure/BotSharp.Core/Tasks/TaskPlugin.cs
+++ b/src/Infrastructure/BotSharp.Core/Tasks/TaskPlugin.cs
@@ -1,5 +1,6 @@
using BotSharp.Abstraction.Plugins.Models;
using BotSharp.Abstraction.Tasks;
+using BotSharp.Abstraction.Users.Enums;
using BotSharp.Core.Tasks.Services;
using Microsoft.Extensions.Configuration;
@@ -19,7 +20,10 @@ public class TaskPlugin : IBotSharpPlugin
public bool AttachMenu(List menu)
{
var section = menu.First(x => x.Label == "Apps");
- menu.Add(new PluginMenuDef("Task", link: "page/task", icon: "bx bx-task", weight: section.Weight + 8));
+ menu.Add(new PluginMenuDef("Task", link: "page/task", icon: "bx bx-task", weight: section.Weight + 8)
+ {
+ Roles = new List { UserRole.Admin }
+ });
return true;
}
diff --git a/src/Infrastructure/BotSharp.Core/Translation/TranslationService.cs b/src/Infrastructure/BotSharp.Core/Translation/TranslationService.cs
index 5c60e108..97e997c7 100644
--- a/src/Infrastructure/BotSharp.Core/Translation/TranslationService.cs
+++ b/src/Infrastructure/BotSharp.Core/Translation/TranslationService.cs
@@ -6,6 +6,7 @@ using BotSharp.Abstraction.Templating;
using BotSharp.Abstraction.Translation.Models;
using System.Collections;
using System.Reflection;
+using System.Text.Encodings.Web;
namespace BotSharp.Core.Translation;
@@ -62,10 +63,18 @@ public class TranslationService : ITranslationService
Id = i + 1,
Text = text
}).ToList();
- var translatedStringList = await InnerTranslate(texts, language, template);
try
{
+ var translatedStringList = await InnerTranslate(texts, language, template);
+
+ int retry = 0;
+ while (translatedStringList.Texts.Length != texts.Count && retry < 3)
+ {
+ translatedStringList = await InnerTranslate(texts, language, template);
+ retry++;
+ }
+
// Override language if it's Unknown, it's used to output the corresponding language.
var states = _services.GetRequiredService();
if (!states.ContainsState(StateConst.LANGUAGE))
@@ -79,7 +88,7 @@ public class TranslationService : ITranslationService
for (var i = 0; i < texts.Count; i++)
{
- map[keys[i]] = translatedTexts[i];
+ map[keys[i]] = translatedTexts[i].Text;
}
clonedData = Assign(clonedData, map);
@@ -302,7 +311,8 @@ public class TranslationService : ITranslationService
///
private async Task InnerTranslate(List texts, string language, string template)
{
- var jsonString = JsonSerializer.Serialize(texts);
+ var options = new JsonSerializerOptions() { Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping };
+ var jsonString = JsonSerializer.Serialize(texts, options);
var translator = new Agent
{
Id = Guid.Empty.ToString(),
diff --git a/src/Infrastructure/BotSharp.Core/Users/Services/UserIdentity.cs b/src/Infrastructure/BotSharp.Core/Users/Services/UserIdentity.cs
index b8acdeea..a8eaac5e 100644
--- a/src/Infrastructure/BotSharp.Core/Users/Services/UserIdentity.cs
+++ b/src/Infrastructure/BotSharp.Core/Users/Services/UserIdentity.cs
@@ -56,4 +56,14 @@ public class UserIdentity : IUserIdentity
return $"{FirstName} {LastName}".Trim();
}
}
+
+ [JsonPropertyName("user_language")]
+ public string? UserLanguage
+ {
+ get
+ {
+ _contextAccessor.HttpContext.Request.Headers.TryGetValue("User-Language", out var languages);
+ return languages.FirstOrDefault();
+ }
+ }
}
diff --git a/src/Infrastructure/BotSharp.Core/Users/Services/UserService.cs b/src/Infrastructure/BotSharp.Core/Users/Services/UserService.cs
index 9508dd2b..19242e39 100644
--- a/src/Infrastructure/BotSharp.Core/Users/Services/UserService.cs
+++ b/src/Infrastructure/BotSharp.Core/Users/Services/UserService.cs
@@ -1,4 +1,6 @@
using BotSharp.Abstraction.Users.Models;
+using BotSharp.Abstraction.Users.Settings;
+using BotSharp.OpenAPI.ViewModels.Users;
using Microsoft.Extensions.Configuration;
using Microsoft.IdentityModel.Tokens;
using NanoidDotNet;
@@ -12,12 +14,17 @@ public class UserService : IUserService
private readonly IServiceProvider _services;
private readonly IUserIdentity _user;
private readonly ILogger _logger;
+ private readonly AccountSetting _setting;
- public UserService(IServiceProvider services, IUserIdentity user, ILogger logger)
+ public UserService(IServiceProvider services,
+ IUserIdentity user,
+ ILogger logger,
+ AccountSetting setting)
{
_services = services;
_user = user;
_logger = logger;
+ _setting = setting;
}
public async Task CreateUser(User user)
@@ -51,11 +58,23 @@ public class UserService : IUserService
record.Salt = Guid.NewGuid().ToString("N");
record.Password = Utilities.HashText(user.Password, record.Salt);
+ if (_setting.NewUserVerification)
+ {
+ record.VerificationCode = Nanoid.Generate(alphabet: "0123456789", size: 6);
+ record.Verified = false;
+ }
+
db.CreateUser(record);
_logger.LogWarning($"Created new user account: {record.Id} {record.UserName}");
Utilities.ClearCache();
+ var hooks = _services.GetServices();
+ foreach (var hook in hooks)
+ {
+ await hook.UserCreated(record);
+ }
+
return record;
}
@@ -71,7 +90,7 @@ public class UserService : IUserService
record = db.GetUserByUserName(id);
}
- User? user = null;
+ User? user = record;
var hooks = _services.GetServices();
if (record == null || record.Source != "internal")
{
@@ -114,6 +133,11 @@ public class UserService : IUserService
return default;
}
+ if (_setting.NewUserVerification && !record.Verified)
+ {
+ return default;
+ }
+
#if !DEBUG
if (Utilities.HashText(password, record.Salt) != record.Password)
{
@@ -200,4 +224,69 @@ public class UserService : IUserService
var user = db.GetUserById(id);
return user;
}
+
+ public async Task ActiveUser(UserActivationModel model)
+ {
+ var id = model.UserName;
+ var db = _services.GetRequiredService();
+ var record = id.Contains("@") ? db.GetUserByEmail(id) : db.GetUserByUserName(id);
+ if (record == null)
+ {
+ record = db.GetUserByUserName(id);
+ }
+
+ if (record == null)
+ {
+ return default;
+ }
+
+ if (record.VerificationCode != model.VerificationCode)
+ {
+ return default;
+ }
+
+ if (record.Verified)
+ {
+ return default;
+ }
+
+ db.UpdateUserVerified(record.Id);
+
+ var accessToken = GenerateJwtToken(record);
+ 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 VerifyUserUnique(string userName)
+ {
+ if (string.IsNullOrEmpty(userName))
+ return false;
+
+ var db = _services.GetRequiredService();
+ var user = db.GetUserByUserName(userName);
+ if (user == null)
+ return true;
+
+ return false;
+ }
+
+ public async Task VerifyEmailUnique(string email)
+ {
+ if (string.IsNullOrEmpty(email))
+ return false;
+
+ var db = _services.GetRequiredService();
+ var emailName = db.GetUserByEmail(email);
+ if (emailName == null)
+ return true;
+
+ return false;
+ }
}
diff --git a/src/Infrastructure/BotSharp.Core/data/agents/01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a/templates/conversation.summary.liquid b/src/Infrastructure/BotSharp.Core/data/agents/01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a/templates/conversation.summary.liquid
new file mode 100644
index 00000000..bb4e764f
--- /dev/null
+++ b/src/Infrastructure/BotSharp.Core/data/agents/01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a/templates/conversation.summary.liquid
@@ -0,0 +1,14 @@
+Please read each conversation in the [CONVERSATIONS] section and provide a summary.
+
+*** Super Important! Please consider every conversation. Do not only consider the recent sentences. ***
+** Please do not respond to the latest conversation.
+** If there are different topics in the conversations, please summarize each topic in different sentences and list them in bullets.
+* Please use concise sentences to summarize each topic.
+* Please do not include excessive details in the summaries.
+* Please use 'user' instead of 'you', 'he' or 'she'.
+
+[CONVERSATIONS]
+
+{% for text in texts -%}
+{{ text }}{{ "\r\n" }}
+{%- endfor %}
\ No newline at end of file
diff --git a/src/Infrastructure/BotSharp.Core/data/agents/01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a/templates/translation_prompt.liquid b/src/Infrastructure/BotSharp.Core/data/agents/01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a/templates/translation_prompt.liquid
index 3d33375b..6b3a8677 100644
--- a/src/Infrastructure/BotSharp.Core/data/agents/01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a/templates/translation_prompt.liquid
+++ b/src/Infrastructure/BotSharp.Core/data/agents/01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a/templates/translation_prompt.liquid
@@ -2,4 +2,5 @@
=====
Translate all the above sentences into {{ language }}.
-Output the translated text in JSON {"input_lang":"original text language", "output_count": {{ text_list_size }}, "output_lang":"{{ language }}", "texts":[{"id": 1, "text":""}]}.
+Output the translated text in JSON {"input_lang":"original text language", "output_count": {{ text_list_size }}, "output_lang":"{{ language }}", "texts":[{"id": 1, "text":""},{"id": 2, "text":""}]}.
+The "output_count" must equal the length of the "texts" array in the output.
diff --git a/src/Infrastructure/BotSharp.OpenAPI/BotSharp.OpenAPI.csproj b/src/Infrastructure/BotSharp.OpenAPI/BotSharp.OpenAPI.csproj
index 22fcc9e0..8f0e9fcc 100644
--- a/src/Infrastructure/BotSharp.OpenAPI/BotSharp.OpenAPI.csproj
+++ b/src/Infrastructure/BotSharp.OpenAPI/BotSharp.OpenAPI.csproj
@@ -18,16 +18,16 @@
-
+
-
+
-
-
+
+
diff --git a/src/Infrastructure/BotSharp.OpenAPI/BotSharpOpenApiExtensions.cs b/src/Infrastructure/BotSharp.OpenAPI/BotSharpOpenApiExtensions.cs
index eabdba84..a1a6dad2 100644
--- a/src/Infrastructure/BotSharp.OpenAPI/BotSharpOpenApiExtensions.cs
+++ b/src/Infrastructure/BotSharp.OpenAPI/BotSharpOpenApiExtensions.cs
@@ -11,8 +11,6 @@ using Microsoft.Net.Http.Headers;
using Microsoft.OpenApi.Models;
using Microsoft.IdentityModel.JsonWebTokens;
using BotSharp.OpenAPI.BackgroundServices;
-using BotSharp.Abstraction.Settings;
-using BotSharp.Abstraction.Google.Settings;
namespace BotSharp.OpenAPI;
@@ -34,12 +32,6 @@ public static class BotSharpOpenApiExtensions
services.AddScoped();
services.AddHostedService();
- services.AddScoped(provider =>
- {
- var settingService = provider.GetRequiredService();
- return settingService.Bind("GoogleApi");
- });
-
// Add bearer authentication
var schema = "MIXED_SCHEME";
var builder = services.AddAuthentication(options =>
diff --git a/src/Infrastructure/BotSharp.OpenAPI/Controllers/AgentController.cs b/src/Infrastructure/BotSharp.OpenAPI/Controllers/AgentController.cs
index bb67de2c..a7b5652d 100644
--- a/src/Infrastructure/BotSharp.OpenAPI/Controllers/AgentController.cs
+++ b/src/Infrastructure/BotSharp.OpenAPI/Controllers/AgentController.cs
@@ -1,4 +1,5 @@
using BotSharp.Abstraction.Agents.Models;
+using BotSharp.Abstraction.Users.Enums;
namespace BotSharp.OpenAPI.Controllers;
@@ -7,11 +8,13 @@ namespace BotSharp.OpenAPI.Controllers;
public class AgentController : ControllerBase
{
private readonly IAgentService _agentService;
+ private readonly IUserIdentity _user;
private readonly IServiceProvider _services;
- public AgentController(IAgentService agentService, IServiceProvider services)
+ public AgentController(IAgentService agentService, IUserIdentity user, IServiceProvider services)
{
_agentService = agentService;
+ _user = user;
_services = services;
}
@@ -23,7 +26,7 @@ public class AgentController : ControllerBase
}
[HttpGet("/agent/{id}")]
- public async Task GetAgent([FromRoute] string id)
+ public async Task GetAgent([FromRoute] string id)
{
var agents = await GetAgents(new AgentFilter
{
@@ -31,6 +34,8 @@ public class AgentController : ControllerBase
});
var targetAgent = agents.Items.FirstOrDefault();
+ if (targetAgent == null) return null;
+
var redirectAgentIds = targetAgent.RoutingRules
.Where(x => !string.IsNullOrEmpty(x.RedirectTo))
.Select(x => x.RedirectTo).ToList();
@@ -45,6 +50,17 @@ public class AgentController : ControllerBase
rule.RedirectToAgentName = found.Name;
}
+
+ var editable = true;
+ var userService = _services.GetRequiredService();
+ var user = await userService.GetUser(_user.Id);
+ if (user?.Role != UserRole.Admin)
+ {
+ var userAgents = _agentService.GetAgentsByUser(user?.Id);
+ editable = userAgents?.Select(x => x.Id)?.Contains(targetAgent.Id) ?? false;
+ }
+
+ targetAgent.Editable = editable;
return targetAgent;
}
@@ -118,4 +134,10 @@ public class AgentController : ControllerBase
model.Id = agentId;
return await _agentService.PatchAgentTemplate(model);
}
+
+ [HttpDelete("/agent/{agentId}")]
+ public async Task DeleteAgent([FromRoute] string agentId)
+ {
+ return await _agentService.DeleteAgent(agentId);
+ }
}
\ No newline at end of file
diff --git a/src/Infrastructure/BotSharp.OpenAPI/Controllers/ConversationController.cs b/src/Infrastructure/BotSharp.OpenAPI/Controllers/ConversationController.cs
index a1d2ca3e..aba09f52 100644
--- a/src/Infrastructure/BotSharp.OpenAPI/Controllers/ConversationController.cs
+++ b/src/Infrastructure/BotSharp.OpenAPI/Controllers/ConversationController.cs
@@ -1,4 +1,6 @@
+using BotSharp.Abstraction.Options;
using BotSharp.Abstraction.Routing;
+using BotSharp.Abstraction.Users.Enums;
namespace BotSharp.OpenAPI.Controllers;
@@ -8,12 +10,16 @@ public class ConversationController : ControllerBase
{
private readonly IServiceProvider _services;
private readonly IUserIdentity _user;
+ private readonly JsonSerializerOptions _jsonOptions;
public ConversationController(IServiceProvider services,
- IUserIdentity user)
+ IUserIdentity user,
+ BotSharpOptions options)
{
_services = services;
_user = user;
+ _jsonOptions = InitJsonOptions(options);
+
}
[HttpPost("/conversation/{agentId}")]
@@ -36,20 +42,23 @@ public class ConversationController : ControllerBase
[HttpPost("/conversations")]
public async Task> GetConversations([FromBody] ConversationFilter filter)
{
- var service = _services.GetRequiredService();
- var conversations = await service.GetConversations(filter);
-
+ var convService = _services.GetRequiredService();
var userService = _services.GetRequiredService();
+ var user = await userService.GetUser(_user.Id);
+ if (user == null)
+ {
+ return new PagedItems();
+ }
+
+ filter.UserId = user.Role != UserRole.Admin ? user.Id : null;
+ var conversations = await convService.GetConversations(filter);
var agentService = _services.GetRequiredService();
- var list = conversations.Items
- .Select(x => ConversationViewModel.FromSession(x))
- .ToList();
+ var list = conversations.Items.Select(x => ConversationViewModel.FromSession(x)).ToList();
foreach (var item in list)
{
- var user = await userService.GetUser(item.User.Id);
+ user = await userService.GetUser(item.User.Id);
item.User = UserViewModel.FromUser(user);
-
var agent = await agentService.GetAgent(item.AgentId);
item.AgentName = agent?.Name;
}
@@ -114,26 +123,42 @@ public class ConversationController : ControllerBase
}
[HttpGet("/conversation/{conversationId}")]
- public async Task GetConversation([FromRoute] string conversationId)
+ public async Task GetConversation([FromRoute] string conversationId)
{
var service = _services.GetRequiredService();
- var conversations = await service.GetConversations(new ConversationFilter
- {
- Id = conversationId
- });
-
var userService = _services.GetRequiredService();
- var result = ConversationViewModel.FromSession(conversations.Items.First());
+ var user = await userService.GetUser(_user.Id);
+ if (user == null)
+ {
+ return null;
+ }
+ var filter = new ConversationFilter
+ {
+ Id = conversationId,
+ UserId = user.Role != UserRole.Admin ? user.Id : null
+ };
+ var conversations = await service.GetConversations(filter);
+ if (conversations.Items.IsNullOrEmpty())
+ {
+ return null;
+ }
+
+ var result = ConversationViewModel.FromSession(conversations.Items.First());
var state = _services.GetRequiredService();
result.States = state.Load(conversationId, isReadOnly: true);
-
- var user = await userService.GetUser(result.User.Id);
result.User = UserViewModel.FromUser(user);
return result;
}
+ [HttpPost("/conversation/summary")]
+ public async Task GetConversationSummary([FromBody] ConversationSummaryModel input)
+ {
+ var service = _services.GetRequiredService();
+ return await service.GetConversationSummary(input.ConversationIds);
+ }
+
[HttpGet("/conversation/{conversationId}/user")]
public async Task GetConversationUser([FromRoute] string conversationId)
{
@@ -166,7 +191,22 @@ public class ConversationController : ControllerBase
[HttpDelete("/conversation/{conversationId}")]
public async Task DeleteConversation([FromRoute] string conversationId)
{
+ var userService = _services.GetRequiredService();
var conversationService = _services.GetRequiredService();
+
+ var user = await userService.GetUser(_user.Id);
+ var filter = new ConversationFilter
+ {
+ Id = conversationId,
+ UserId = user.Role != UserRole.Admin ? user.Id : null
+ };
+ var conversations = await conversationService.GetConversations(filter);
+
+ if (conversations.Items.IsNullOrEmpty())
+ {
+ return false;
+ }
+
var response = await conversationService.DeleteConversations(new List { conversationId });
return response;
}
@@ -179,15 +219,7 @@ public class ConversationController : ControllerBase
return response;
}
- private void SetStates(IConversationService conv, NewMessageModel input)
- {
- conv.States.SetState("channel", input.Channel, source: StateSource.External)
- .SetState("provider", input.Provider, source: StateSource.External)
- .SetState("model", input.Model, source: StateSource.External)
- .SetState("temperature", input.Temperature, source: StateSource.External)
- .SetState("sampling_factor", input.SamplingFactor, source: StateSource.External);
- }
-
+ #region Send message
[HttpPost("/conversation/{agentId}/{conversationId}")]
public async Task SendMessage([FromRoute] string agentId,
[FromRoute] string conversationId,
@@ -251,13 +283,19 @@ public class ConversationController : ControllerBase
await conv.TruncateConversation(conversationId, input.TruncateMessageId, inputMsg.MessageId);
}
+ var state = _services.GetRequiredService();
+
var routing = _services.GetRequiredService();
routing.Context.SetMessageId(conversationId, inputMsg.MessageId);
conv.SetConversationId(conversationId, input.States);
SetStates(conv, input);
- var response = new ChatResponseModel();
+ var response = new ChatResponseModel
+ {
+ ConversationId = conversationId,
+ MessageId = inputMsg.MessageId,
+ };
Response.StatusCode = 200;
Response.Headers.Append(Microsoft.Net.Http.Headers.HeaderNames.ContentType, "text/event-stream");
@@ -266,6 +304,7 @@ public class ConversationController : ControllerBase
await conv.SendMessage(agentId, inputMsg,
replyMessage: input.Postback,
+ // responsed generated
async msg =>
{
response.Text = !string.IsNullOrEmpty(msg.SecondaryContent) ? msg.SecondaryContent : msg.Content;
@@ -273,35 +312,107 @@ public class ConversationController : ControllerBase
response.RichContent = msg.SecondaryRichContent ?? msg.RichContent;
response.Instruction = msg.Instruction;
response.Data = msg.Data;
+ response.States = state.GetStates();
- await OnChunkReceived(Response, msg);
+ await OnChunkReceived(Response, response);
},
+ // executing
async msg =>
{
- var message = new RoleDialogModel(AgentRole.Function, msg.Content)
+ var indicator = new ChatResponseModel
{
- FunctionArgs = msg.FunctionArgs,
- FunctionName = msg.FunctionName,
- Indication = msg.Indication
+ ConversationId = conversationId,
+ MessageId = msg.MessageId,
+ Text = msg.Indication,
+ Function = "indicating",
+ Instruction = msg.Instruction,
+ States = new Dictionary()
};
- await OnChunkReceived(Response, message);
+ await OnChunkReceived(Response, indicator);
},
+ // executed
async msg =>
{
});
- var state = _services.GetRequiredService();
response.States = state.GetStates();
response.MessageId = inputMsg.MessageId;
response.ConversationId = conversationId;
// await OnEventCompleted(Response);
}
+ #endregion
- private async Task OnChunkReceived(HttpResponse response, RoleDialogModel message)
+ #region Files and attachments
+ [HttpPost("/conversation/{conversationId}/attachments")]
+ public IActionResult UploadAttachments([FromRoute] string conversationId,
+ IFormFile[] files)
{
- var json = JsonSerializer.Serialize(message);
+ if (files != null && files.Length > 0)
+ {
+ var fileService = _services.GetRequiredService();
+ var dir = fileService.GetDirectory(conversationId);
+ foreach (var file in files)
+ {
+ // Save the file, process it, etc.
+ var fileName = ContentDispositionHeaderValue.Parse(file.ContentDisposition).FileName.Trim('"');
+ var filePath = Path.Combine(dir, fileName);
+
+ using (var stream = new FileStream(filePath, FileMode.Create))
+ {
+ file.CopyTo(stream);
+ }
+ }
+
+ return Ok(new { message = "File uploaded successfully." });
+ }
+
+ return BadRequest(new { message = "Invalid file." });
+ }
+
+ [HttpGet("/conversation/{conversationId}/files/{messageId}")]
+ public IEnumerable GetMessageFiles([FromRoute] string conversationId, [FromRoute] string messageId)
+ {
+ var fileService = _services.GetRequiredService();
+ var files = fileService.GetMessageFiles(conversationId, new List { messageId });
+ return files?.Select(x => MessageFileViewModel.Transform(x))?.ToList() ?? new List();
+ }
+
+ [HttpGet("/conversation/{conversationId}/message/{messageId}/file/{fileName}")]
+ public IActionResult GetMessageFile([FromRoute] string conversationId, [FromRoute] string messageId, [FromRoute] string fileName)
+ {
+ var fileService = _services.GetRequiredService();
+ var file = fileService.GetMessageFile(conversationId, messageId, fileName);
+ if (string.IsNullOrEmpty(file))
+ {
+ return NotFound();
+ }
+ return BuildFileResult(file);
+ }
+ #endregion
+
+ #region Private methods
+ private void SetStates(IConversationService conv, NewMessageModel input)
+ {
+ conv.States.SetState("channel", input.Channel, source: StateSource.External)
+ .SetState("provider", input.Provider, source: StateSource.External)
+ .SetState("model", input.Model, source: StateSource.External)
+ .SetState("temperature", input.Temperature, source: StateSource.External)
+ .SetState("sampling_factor", input.SamplingFactor, source: StateSource.External);
+ }
+
+ private FileContentResult BuildFileResult(string file)
+ {
+ using Stream stream = System.IO.File.Open(file, FileMode.Open, FileAccess.Read, FileShare.Read);
+ var bytes = new byte[stream.Length];
+ stream.Read(bytes, 0, (int)stream.Length);
+ return File(bytes, "application/octet-stream", Path.GetFileName(file));
+ }
+
+ private async Task OnChunkReceived(HttpResponse response, ChatResponseModel message)
+ {
+ var json = JsonSerializer.Serialize(message, _jsonOptions);
var buffer = Encoding.UTF8.GetBytes($"data:{json}\n");
await response.Body.WriteAsync(buffer, 0, buffer.Length);
@@ -319,4 +430,25 @@ public class ConversationController : ControllerBase
buffer = Encoding.UTF8.GetBytes("\n");
await response.Body.WriteAsync(buffer, 0, buffer.Length);
}
+
+ private JsonSerializerOptions InitJsonOptions(BotSharpOptions options)
+ {
+ var jsonOption = new JsonSerializerOptions
+ {
+ PropertyNameCaseInsensitive = true,
+ PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
+ AllowTrailingCommas = true
+ };
+
+ if (options?.JsonSerializerOptions != null)
+ {
+ foreach (var option in options.JsonSerializerOptions.Converters)
+ {
+ jsonOption.Converters.Add(option);
+ }
+ }
+
+ return jsonOption;
+ }
+ #endregion
}
diff --git a/src/Infrastructure/BotSharp.OpenAPI/Controllers/FileController.cs b/src/Infrastructure/BotSharp.OpenAPI/Controllers/FileController.cs
index cfae602c..36dddebd 100644
--- a/src/Infrastructure/BotSharp.OpenAPI/Controllers/FileController.cs
+++ b/src/Infrastructure/BotSharp.OpenAPI/Controllers/FileController.cs
@@ -10,54 +10,4 @@ public class FileController : ControllerBase
{
_services = services;
}
-
- [HttpPost("/conversation/{conversationId}/attachments")]
- public IActionResult UploadAttachments([FromRoute] string conversationId,
- IFormFile[] files)
- {
- if (files != null && files.Length > 0)
- {
- var fileService = _services.GetRequiredService();
- var dir = fileService.GetDirectory(conversationId);
- foreach (var file in files)
- {
- // Save the file, process it, etc.
- var fileName = ContentDispositionHeaderValue.Parse(file.ContentDisposition).FileName.Trim('"');
- var filePath = Path.Combine(dir, fileName);
-
- using (var stream = new FileStream(filePath, FileMode.Create))
- {
- file.CopyTo(stream);
- }
- }
-
- return Ok(new { message = "File uploaded successfully." });
- }
-
- return BadRequest(new { message = "Invalid file." });
- }
-
- [HttpGet("/conversation/{conversationId}/files/{messageId}")]
- public IEnumerable GetMessageFiles([FromRoute] string conversationId, [FromRoute] string messageId)
- {
- var fileService = _services.GetRequiredService();
- var files = fileService.GetMessageFiles(conversationId, new List { messageId });
- return files?.Select(x => MessageFileViewModel.Transform(x))?.ToList() ?? new List();
- }
-
- [HttpGet("/conversation/{conversationId}/message/{messageId}/file/{fileName}")]
- public async Task GetMessageFile([FromRoute] string conversationId, [FromRoute] string messageId, [FromRoute] string fileName)
- {
- var fileService = _services.GetRequiredService();
- var file = fileService.GetMessageFile(conversationId, messageId, fileName);
- if (string.IsNullOrEmpty(file))
- {
- return NotFound();
- }
-
- using Stream stream = System.IO.File.Open(file, FileMode.Open, FileAccess.Read, FileShare.Read);
- var bytes = new byte[stream.Length];
- stream.Read(bytes, 0, (int)stream.Length);
- return File(bytes, "application/octet-stream", Path.GetFileName(file));
- }
}
diff --git a/src/Infrastructure/BotSharp.OpenAPI/Controllers/PluginController.cs b/src/Infrastructure/BotSharp.OpenAPI/Controllers/PluginController.cs
index 2c231e16..342f39fb 100644
--- a/src/Infrastructure/BotSharp.OpenAPI/Controllers/PluginController.cs
+++ b/src/Infrastructure/BotSharp.OpenAPI/Controllers/PluginController.cs
@@ -1,4 +1,5 @@
using BotSharp.Abstraction.Plugins.Models;
+using BotSharp.Abstraction.Users.Enums;
using BotSharp.Core.Plugins;
namespace BotSharp.OpenAPI.Controllers;
@@ -8,23 +9,32 @@ namespace BotSharp.OpenAPI.Controllers;
public class PluginController : ControllerBase
{
private readonly IServiceProvider _services;
+ private readonly IUserIdentity _user;
private readonly PluginSettings _settings;
- public PluginController(IServiceProvider services, PluginSettings settings)
+ public PluginController(IServiceProvider services, IUserIdentity user, PluginSettings settings)
{
_services = services;
+ _user = user;
_settings = settings;
}
[HttpGet("/plugins")]
- public PagedItems GetPlugins([FromQuery] PluginFilter filter)
+ public async Task> GetPlugins([FromQuery] PluginFilter filter)
{
+ var userService = _services.GetRequiredService();
+ var user = await userService.GetUser(_user.Id);
+ if (user?.Role != UserRole.Admin)
+ {
+ return new PagedItems();
+ }
+
var loader = _services.GetRequiredService();
return loader.GetPagedPlugins(_services, filter);
}
[HttpGet("/plugin/menu")]
- public List GetPluginMenu()
+ public async Task> GetPluginMenu()
{
var menu = new List
{
@@ -33,11 +43,18 @@ public class PluginController : ControllerBase
IsHeader = true,
},
new PluginMenuDef("System", weight: 30)
- {
- IsHeader = true
+ {
+ IsHeader = true,
+ Roles = new List { UserRole.Admin }
},
- new PluginMenuDef("Plugins", link: "page/plugin", icon: "bx bx-plug", weight: 31),
- new PluginMenuDef("Settings", link: "page/setting", icon: "bx bx-cog", weight: 32),
+ new PluginMenuDef("Plugins", link: "page/plugin", icon: "bx bx-plug", weight: 31)
+ {
+ Roles = new List { UserRole.Admin }
+ },
+ new PluginMenuDef("Settings", link: "page/setting", icon: "bx bx-cog", weight: 32)
+ {
+ Roles = new List { UserRole.Admin }
+ }
};
var loader = _services.GetRequiredService();
@@ -49,6 +66,10 @@ public class PluginController : ControllerBase
}
plugin.Module.AttachMenu(menu);
}
+
+ var userService = _services.GetRequiredService();
+ var user = await userService.GetUser(_user.Id);
+ menu = loader.GetPluginMenuByRoles(menu, user?.Role);
menu = menu.OrderBy(x => x.Weight).ToList();
return menu;
}
diff --git a/src/Infrastructure/BotSharp.OpenAPI/Controllers/UserController.cs b/src/Infrastructure/BotSharp.OpenAPI/Controllers/UserController.cs
index e4201aa8..682b963b 100644
--- a/src/Infrastructure/BotSharp.OpenAPI/Controllers/UserController.cs
+++ b/src/Infrastructure/BotSharp.OpenAPI/Controllers/UserController.cs
@@ -61,6 +61,18 @@ public class UserController : ControllerBase
return UserViewModel.FromUser(createdUser);
}
+ [AllowAnonymous]
+ [HttpPost("/user/activate")]
+ public async Task> ActivateUser(UserActivationModel model)
+ {
+ var token = await _userService.ActiveUser(model);
+ if (token == null)
+ {
+ return Unauthorized();
+ }
+ return Ok(token);
+ }
+
[HttpGet("/user/me")]
public async Task GetMyUserProfile()
{
@@ -82,4 +94,48 @@ public class UserController : ControllerBase
}
return UserViewModel.FromUser(user);
}
+
+ [HttpGet("/user/name/existing")]
+ public async Task VerifyUserUnique([FromQuery] string userName)
+ {
+ return await _userService.VerifyUserUnique(userName);
+ }
+
+ [HttpGet("/user/email/existing")]
+ public async Task VerifyEmailUnique([FromQuery] string email)
+ {
+ return await _userService.VerifyEmailUnique(email);
+ }
+
+ #region Avatar
+ [HttpPost("/user/avatar")]
+ public bool UploadUserAvatar([FromBody] BotSharpFile file)
+ {
+ var fileService = _services.GetRequiredService();
+ return fileService.SaveUserAvatar(file);
+ }
+
+ [HttpGet("/user/avatar")]
+ public IActionResult GetUserAvatar()
+ {
+ var fileService = _services.GetRequiredService();
+ var file = fileService.GetUserAvatar();
+ if (string.IsNullOrEmpty(file))
+ {
+ return NotFound();
+ }
+ return BuildFileResult(file);
+ }
+ #endregion
+
+
+ #region Private methods
+ private FileContentResult BuildFileResult(string file)
+ {
+ using Stream stream = System.IO.File.Open(file, FileMode.Open, FileAccess.Read, FileShare.Read);
+ var bytes = new byte[stream.Length];
+ stream.Read(bytes, 0, (int)stream.Length);
+ return File(bytes, "application/octet-stream", Path.GetFileName(file));
+ }
+ #endregion
}
diff --git a/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Agents/AgentViewModel.cs b/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Agents/AgentViewModel.cs
index 536f5b5d..9d87a3e2 100644
--- a/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Agents/AgentViewModel.cs
+++ b/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Agents/AgentViewModel.cs
@@ -42,6 +42,8 @@ public class AgentViewModel
public PluginDef Plugin { get; set; }
+ public bool Editable { get; set; }
+
[JsonPropertyName("created_datetime")]
public DateTime CreatedDateTime { get; set; }
diff --git a/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Conversations/ConversationSummaryModel.cs b/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Conversations/ConversationSummaryModel.cs
new file mode 100644
index 00000000..0854ab2a
--- /dev/null
+++ b/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Conversations/ConversationSummaryModel.cs
@@ -0,0 +1,9 @@
+using System.Text.Json.Serialization;
+
+namespace BotSharp.OpenAPI.ViewModels.Conversations;
+
+public class ConversationSummaryModel
+{
+ [JsonPropertyName("conversation_ids")]
+ public List ConversationIds { get; set; } = new List();
+}
diff --git a/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Users/UserViewModel.cs b/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Users/UserViewModel.cs
index eeb1a8a2..8d28cf03 100644
--- a/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Users/UserViewModel.cs
+++ b/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Users/UserViewModel.cs
@@ -19,6 +19,7 @@ public class UserViewModel
public string Source { get; set; }
[JsonPropertyName("external_id")]
public string? ExternalId { get; set; }
+ public string Avatar { get; set; } = "/user/avatar";
[JsonPropertyName("create_date")]
public DateTime CreateDate { get; set; }
[JsonPropertyName("update_date")]
@@ -47,7 +48,8 @@ public class UserViewModel
Source = user.Source,
ExternalId = user.ExternalId,
CreateDate = user.CreatedTime,
- UpdateDate = user.UpdatedTime
+ UpdateDate = user.UpdatedTime,
+ Avatar = "/user/avatar"
};
}
}
diff --git a/src/Plugins/BotSharp.Plugin.ChatHub/WebSocketsMiddleware.cs b/src/Plugins/BotSharp.Plugin.ChatHub/WebSocketsMiddleware.cs
index e20f8602..79cb803a 100644
--- a/src/Plugins/BotSharp.Plugin.ChatHub/WebSocketsMiddleware.cs
+++ b/src/Plugins/BotSharp.Plugin.ChatHub/WebSocketsMiddleware.cs
@@ -14,13 +14,11 @@ public class WebSocketsMiddleware
public async Task Invoke(HttpContext httpContext)
{
- var request = httpContext.Request;;
- var messageFileRegex = new Regex(@"/conversation/[a-z0-9-]+/message/[a-z0-9-]+/file/[a-z0-9-]+", RegexOptions.IgnoreCase);
+ var request = httpContext.Request;
// web sockets cannot pass headers so we must take the access token from query param and
// add it to the header before authentication middleware runs
- if ((request.Path.StartsWithSegments("/chatHub", StringComparison.OrdinalIgnoreCase)
- || messageFileRegex.IsMatch(request.Path.Value ?? string.Empty)) &&
+ if ((VerifyChatHubRequest(request) || VerifyGetRequest(request)) &&
request.Query.TryGetValue("access_token", out var accessToken))
{
request.Headers["Authorization"] = $"Bearer {accessToken}";
@@ -28,4 +26,20 @@ public class WebSocketsMiddleware
await _next(httpContext);
}
+
+ private bool VerifyChatHubRequest(HttpRequest request)
+ {
+ return request.Path.StartsWithSegments("/chatHub", StringComparison.OrdinalIgnoreCase);
+ }
+
+ private bool VerifyGetRequest(HttpRequest request)
+ {
+ var regexes = new List
+ {
+ new Regex(@"/conversation/[a-z0-9-]+/message/[a-z0-9-]+/file/[a-z0-9-]+", RegexOptions.IgnoreCase),
+ new Regex(@"/user/avatar", RegexOptions.IgnoreCase)
+ };
+
+ return request.Method.IsEqualTo("GET") && regexes.Any(x => x.IsMatch(request.Path.Value ?? string.Empty));
+ }
}
diff --git a/src/Plugins/BotSharp.Plugin.MongoStorage/BotSharp.Plugin.MongoStorage.csproj b/src/Plugins/BotSharp.Plugin.MongoStorage/BotSharp.Plugin.MongoStorage.csproj
index 41c63ad9..9b1d2178 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/UserDocument.cs b/src/Plugins/BotSharp.Plugin.MongoStorage/Collections/UserDocument.cs
index c21a6e08..c277b9f3 100644
--- a/src/Plugins/BotSharp.Plugin.MongoStorage/Collections/UserDocument.cs
+++ b/src/Plugins/BotSharp.Plugin.MongoStorage/Collections/UserDocument.cs
@@ -13,7 +13,8 @@ public class UserDocument : MongoBase
public string Source { get; set; } = "internal";
public string? ExternalId { get; set; }
public string Role { get; set; }
-
+ public string? VerificationCode { get; set; }
+ public bool Verified { get; set; }
public DateTime CreatedTime { get; set; }
public DateTime UpdatedTime { get; set; }
@@ -30,7 +31,9 @@ public class UserDocument : MongoBase
Salt = Salt,
Source = Source,
ExternalId = ExternalId,
- Role = Role
+ Role = Role,
+ VerificationCode = VerificationCode,
+ Verified = Verified,
};
}
}
\ No newline at end of file
diff --git a/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.User.cs b/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.User.cs
index 1b6b8b77..1a5211f4 100644
--- a/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.User.cs
+++ b/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.User.cs
@@ -40,10 +40,20 @@ public partial class MongoRepository
Source = user.Source,
ExternalId = user.ExternalId,
Role = user.Role,
+ VerificationCode = user.VerificationCode,
+ Verified = user.Verified,
CreatedTime = DateTime.UtcNow,
UpdatedTime = DateTime.UtcNow
};
_dc.Users.InsertOne(userCollection);
}
+
+ public void UpdateUserVerified(string userId)
+ {
+ var filter = Builders.Filter.Eq(x => x.Id, userId);
+ var update = Builders.Update.Set(x => x.Verified, true)
+ .Set(x => x.UpdatedTime, DateTime.UtcNow);
+ _dc.Users.UpdateOne(filter, update);
+ }
}
diff --git a/src/Plugins/BotSharp.Plugin.PaddleSharp/BotSharp.Plugin.PaddleSharp.csproj b/src/Plugins/BotSharp.Plugin.PaddleSharp/BotSharp.Plugin.PaddleSharp.csproj
index 6dcaf95e..ef637413 100644
--- a/src/Plugins/BotSharp.Plugin.PaddleSharp/BotSharp.Plugin.PaddleSharp.csproj
+++ b/src/Plugins/BotSharp.Plugin.PaddleSharp/BotSharp.Plugin.PaddleSharp.csproj
@@ -1,4 +1,4 @@
-
+
netstandard2.1
@@ -12,14 +12,14 @@
-
-
+
+
-
+
diff --git a/src/Plugins/BotSharp.Plugin.Qdrant/BotSharp.Plugin.Qdrant.csproj b/src/Plugins/BotSharp.Plugin.Qdrant/BotSharp.Plugin.Qdrant.csproj
index 693c7885..bdcd40d5 100644
--- a/src/Plugins/BotSharp.Plugin.Qdrant/BotSharp.Plugin.Qdrant.csproj
+++ b/src/Plugins/BotSharp.Plugin.Qdrant/BotSharp.Plugin.Qdrant.csproj
@@ -11,7 +11,7 @@
-
+
diff --git a/src/Plugins/BotSharp.Plugin.SemanticKernel/BotSharp.Plugin.SemanticKernel.csproj b/src/Plugins/BotSharp.Plugin.SemanticKernel/BotSharp.Plugin.SemanticKernel.csproj
index 9d790284..c4051422 100644
--- a/src/Plugins/BotSharp.Plugin.SemanticKernel/BotSharp.Plugin.SemanticKernel.csproj
+++ b/src/Plugins/BotSharp.Plugin.SemanticKernel/BotSharp.Plugin.SemanticKernel.csproj
@@ -1,4 +1,4 @@
-
+
netstandard2.1
@@ -11,8 +11,8 @@
-
-
+
+
diff --git a/src/Plugins/BotSharp.Plugin.SemanticKernel/SemanticKernelMemoryStoreProvider.cs b/src/Plugins/BotSharp.Plugin.SemanticKernel/SemanticKernelMemoryStoreProvider.cs
index 444cab1e..7df2c0cd 100644
--- a/src/Plugins/BotSharp.Plugin.SemanticKernel/SemanticKernelMemoryStoreProvider.cs
+++ b/src/Plugins/BotSharp.Plugin.SemanticKernel/SemanticKernelMemoryStoreProvider.cs
@@ -9,13 +9,13 @@ namespace BotSharp.Plugin.SemanticKernel
{
internal class SemanticKernelMemoryStoreProvider : IVectorDb
{
-#pragma warning disable SKEXP0003 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
+#pragma warning disable SKEXP0001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
private readonly IMemoryStore _memoryStore;
-#pragma warning restore SKEXP0003 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
+#pragma warning restore SKEXP0001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
-#pragma warning disable SKEXP0003 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
+#pragma warning disable SKEXP0001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
public SemanticKernelMemoryStoreProvider(IMemoryStore memoryStore)
-#pragma warning restore SKEXP0003 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
+#pragma warning restore SKEXP0001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
{
this._memoryStore = memoryStore;
}
@@ -50,9 +50,9 @@ namespace BotSharp.Plugin.SemanticKernel
public async Task Upsert(string collectionName, int id, float[] vector, string text)
{
-#pragma warning disable SKEXP0003 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
+#pragma warning disable SKEXP0001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
await _memoryStore.UpsertAsync(collectionName, MemoryRecord.LocalRecord(id.ToString(), text, null, vector));
-#pragma warning restore SKEXP0003 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
+#pragma warning restore SKEXP0001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
}
}
}
diff --git a/src/Plugins/BotSharp.Plugin.SqlDriver/BotSharp.Plugin.SqlDriver.csproj b/src/Plugins/BotSharp.Plugin.SqlDriver/BotSharp.Plugin.SqlDriver.csproj
index 383a9f4e..afb53ae8 100644
--- a/src/Plugins/BotSharp.Plugin.SqlDriver/BotSharp.Plugin.SqlDriver.csproj
+++ b/src/Plugins/BotSharp.Plugin.SqlDriver/BotSharp.Plugin.SqlDriver.csproj
@@ -33,7 +33,7 @@
-
+
diff --git a/src/Plugins/BotSharp.Plugin.Twilio/BotSharp.Plugin.Twilio.csproj b/src/Plugins/BotSharp.Plugin.Twilio/BotSharp.Plugin.Twilio.csproj
index c8f1a6f5..ed99a926 100644
--- a/src/Plugins/BotSharp.Plugin.Twilio/BotSharp.Plugin.Twilio.csproj
+++ b/src/Plugins/BotSharp.Plugin.Twilio/BotSharp.Plugin.Twilio.csproj
@@ -9,7 +9,7 @@
-
+
diff --git a/src/Plugins/BotSharp.Plugin.WeChat/BotSharp.Plugin.WeChat.csproj b/src/Plugins/BotSharp.Plugin.WeChat/BotSharp.Plugin.WeChat.csproj
index b2c19ae1..85c04581 100644
--- a/src/Plugins/BotSharp.Plugin.WeChat/BotSharp.Plugin.WeChat.csproj
+++ b/src/Plugins/BotSharp.Plugin.WeChat/BotSharp.Plugin.WeChat.csproj
@@ -27,7 +27,7 @@
-
+
diff --git a/src/Plugins/BotSharp.Plugin.WebDriver/BotSharp.Plugin.WebDriver.csproj b/src/Plugins/BotSharp.Plugin.WebDriver/BotSharp.Plugin.WebDriver.csproj
index b03f585d..96f97705 100644
--- a/src/Plugins/BotSharp.Plugin.WebDriver/BotSharp.Plugin.WebDriver.csproj
+++ b/src/Plugins/BotSharp.Plugin.WebDriver/BotSharp.Plugin.WebDriver.csproj
@@ -17,8 +17,8 @@
-
-
+
+
diff --git a/src/Plugins/BotSharp.Plugin.WebDriver/Drivers/PlaywrightDriver/PlaywrightInstance.cs b/src/Plugins/BotSharp.Plugin.WebDriver/Drivers/PlaywrightDriver/PlaywrightInstance.cs
index a801400b..e7fd5908 100644
--- a/src/Plugins/BotSharp.Plugin.WebDriver/Drivers/PlaywrightDriver/PlaywrightInstance.cs
+++ b/src/Plugins/BotSharp.Plugin.WebDriver/Drivers/PlaywrightDriver/PlaywrightInstance.cs
@@ -40,11 +40,12 @@ public class PlaywrightInstance : IDisposable
Channel = "chrome",
IgnoreDefaultArgs = new[]
{
- "--disable-infobars"
+ "--enable-automation",
},
Args = new[]
{
"--disable-infobars",
+ "--no-sandbox",
// "--start-maximized"
}
});
@@ -104,6 +105,6 @@ public class PlaywrightInstance : IDisposable
public void Dispose()
{
_contexts.Clear();
- _playwright.Dispose();
+ _playwright?.Dispose();
}
}
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 dd4600c9..91a99aa8 100644
--- a/src/Plugins/BotSharp.Plugin.WebDriver/Drivers/PlaywrightDriver/PlaywrightWebDriver.DoAction.cs
+++ b/src/Plugins/BotSharp.Plugin.WebDriver/Drivers/PlaywrightDriver/PlaywrightWebDriver.DoAction.cs
@@ -28,6 +28,11 @@ public partial class PlaywrightWebDriver
else if (action.Action == BroswerActionEnum.InputText)
{
await locator.FillAsync(action.Content);
+
+ if (action.PressKey != null)
+ {
+ await locator.PressAsync(action.PressKey);
+ }
}
}
}
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 5513e89f..f830bc9f 100644
--- a/src/Plugins/BotSharp.Plugin.WebDriver/Drivers/PlaywrightDriver/PlaywrightWebDriver.GoToPage.cs
+++ b/src/Plugins/BotSharp.Plugin.WebDriver/Drivers/PlaywrightDriver/PlaywrightWebDriver.GoToPage.cs
@@ -5,13 +5,29 @@ public partial class PlaywrightWebDriver
public async Task GoToPage(string contextId, string url, bool openNewTab = false)
{
var result = new BrowserActionResult();
+ var context = await _instance.InitInstance(contextId);
try
{
+ // Check if the page is already open
+ foreach (var p in context.Pages)
+ {
+ if (p.Url == url)
+ {
+ result.Body = await p.ContentAsync();
+ result.IsSuccess = true;
+ await p.BringToFrontAsync();
+ return result;
+ }
+ }
+
var page = openNewTab ? await _instance.NewPage(contextId) :
_instance.GetPage(contextId);
var response = await page.GotoAsync(url);
await page.WaitForLoadStateAsync(LoadState.DOMContentLoaded);
- await page.WaitForLoadStateAsync(LoadState.NetworkIdle);
+ await page.WaitForLoadStateAsync(LoadState.NetworkIdle, new PageWaitForLoadStateOptions
+ {
+ Timeout = 1000 * 60 * 5
+ });
if (response.Status == 200)
{
diff --git a/src/Plugins/BotSharp.Plugin.WebDriver/Drivers/PlaywrightDriver/PlaywrightWebDriver.HttpRequest.cs b/src/Plugins/BotSharp.Plugin.WebDriver/Drivers/PlaywrightDriver/PlaywrightWebDriver.HttpRequest.cs
index a5a59334..a769332b 100644
--- a/src/Plugins/BotSharp.Plugin.WebDriver/Drivers/PlaywrightDriver/PlaywrightWebDriver.HttpRequest.cs
+++ b/src/Plugins/BotSharp.Plugin.WebDriver/Drivers/PlaywrightDriver/PlaywrightWebDriver.HttpRequest.cs
@@ -4,7 +4,7 @@ namespace BotSharp.Plugin.WebDriver.Drivers.PlaywrightDriver;
public partial class PlaywrightWebDriver
{
- public async Task SendHttpRequest(string contextId, HttpRequestParams args)
+ public async Task SendHttpRequest(MessageInfo message, HttpRequestParams args)
{
var result = new BrowserActionResult();
@@ -27,7 +27,8 @@ public partial class PlaywrightWebDriver
try
{
- var response = await EvaluateScript