diff --git a/BotSharp.sln b/BotSharp.sln
index 93f289ba..52b3f9d0 100644
--- a/BotSharp.sln
+++ b/BotSharp.sln
@@ -117,7 +117,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BotSharp.Plugin.Graph", "sr
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BotSharp.Plugin.AudioHandler", "src\Plugins\BotSharp.Plugin.AudioHandler\BotSharp.Plugin.AudioHandler.csproj", "{F57F4862-F8D4-44A1-AC12-5C131B5C9785}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BotSharp.Core.SideCar", "src\Infrastructure\BotSharp.Core.SideCar\BotSharp.Core.SideCar.csproj", "{6D3A54F9-4792-41DB-BE7D-4F7B1D918EAE}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BotSharp.Core.SideCar", "src\Infrastructure\BotSharp.Core.SideCar\BotSharp.Core.SideCar.csproj", "{6D3A54F9-4792-41DB-BE7D-4F7B1D918EAE}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BotSharp.Plugin.VertexAI", "src\Plugins\BotSharp.Plugin.LangChain\BotSharp.Plugin.VertexAI.csproj", "{7DA2DCD0-551B-432E-AA5C-22DDD3ED459B}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -479,6 +481,14 @@ Global
{6D3A54F9-4792-41DB-BE7D-4F7B1D918EAE}.Release|Any CPU.Build.0 = Release|Any CPU
{6D3A54F9-4792-41DB-BE7D-4F7B1D918EAE}.Release|x64.ActiveCfg = Release|Any CPU
{6D3A54F9-4792-41DB-BE7D-4F7B1D918EAE}.Release|x64.Build.0 = Release|Any CPU
+ {7DA2DCD0-551B-432E-AA5C-22DDD3ED459B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {7DA2DCD0-551B-432E-AA5C-22DDD3ED459B}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {7DA2DCD0-551B-432E-AA5C-22DDD3ED459B}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {7DA2DCD0-551B-432E-AA5C-22DDD3ED459B}.Debug|x64.Build.0 = Debug|Any CPU
+ {7DA2DCD0-551B-432E-AA5C-22DDD3ED459B}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {7DA2DCD0-551B-432E-AA5C-22DDD3ED459B}.Release|Any CPU.Build.0 = Release|Any CPU
+ {7DA2DCD0-551B-432E-AA5C-22DDD3ED459B}.Release|x64.ActiveCfg = Release|Any CPU
+ {7DA2DCD0-551B-432E-AA5C-22DDD3ED459B}.Release|x64.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -536,6 +546,7 @@ Global
{EBFE97DA-D0BA-48BA-8B5D-083B60348D1D} = {97A0B191-64D7-4F8A-BFE8-1BFCC5E247E1}
{F57F4862-F8D4-44A1-AC12-5C131B5C9785} = {51AFE054-AE99-497D-A593-69BAEFB5106F}
{6D3A54F9-4792-41DB-BE7D-4F7B1D918EAE} = {E29DC6C4-5E57-48C5-BCB0-6B8F84782749}
+ {7DA2DCD0-551B-432E-AA5C-22DDD3ED459B} = {D5293208-2BEF-42FC-A64C-5954F61720BA}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {A9969D89-C98B-40A5-A12B-FC87E55B3A19}
diff --git a/src/Infrastructure/BotSharp.Abstraction/Knowledges/IKnowledgeService.cs b/src/Infrastructure/BotSharp.Abstraction/Knowledges/IKnowledgeService.cs
index 2313b2ba..2bc70dc2 100644
--- a/src/Infrastructure/BotSharp.Abstraction/Knowledges/IKnowledgeService.cs
+++ b/src/Infrastructure/BotSharp.Abstraction/Knowledges/IKnowledgeService.cs
@@ -30,7 +30,7 @@ public interface IKnowledgeService
///
///
///
- Task UploadDocumentsToKnowledge(string collectionName, IEnumerable files);
+ Task UploadDocumentsToKnowledge(string collectionName, IEnumerable files, ChunkOption? option = null);
///
/// Save document content to knowledgebase without saving the document
///
diff --git a/src/Infrastructure/BotSharp.Abstraction/Knowledges/Models/ChunkOption.cs b/src/Infrastructure/BotSharp.Abstraction/Knowledges/Models/ChunkOption.cs
index 936a41fb..72c474bf 100644
--- a/src/Infrastructure/BotSharp.Abstraction/Knowledges/Models/ChunkOption.cs
+++ b/src/Infrastructure/BotSharp.Abstraction/Knowledges/Models/ChunkOption.cs
@@ -13,4 +13,15 @@ public class ChunkOption
public int Conjunction { get; set; }
public bool SplitByWord { get; set; }
+
+
+ public static ChunkOption Default()
+ {
+ return new ChunkOption
+ {
+ Size = 1024,
+ Conjunction = 12,
+ SplitByWord = true,
+ };
+ }
}
diff --git a/src/Infrastructure/BotSharp.Abstraction/Plugins/Models/PluginFilter.cs b/src/Infrastructure/BotSharp.Abstraction/Plugins/Models/PluginFilter.cs
index e7798d6c..0bd9a42d 100644
--- a/src/Infrastructure/BotSharp.Abstraction/Plugins/Models/PluginFilter.cs
+++ b/src/Infrastructure/BotSharp.Abstraction/Plugins/Models/PluginFilter.cs
@@ -4,5 +4,6 @@ namespace BotSharp.Abstraction.Plugins.Models
{
public Pagination Pager { get; set; } = new Pagination();
public IEnumerable? Names { get; set; }
+ public string? SimilarName { get; set; }
}
}
diff --git a/src/Infrastructure/BotSharp.Abstraction/Repositories/Filters/AgentFilter.cs b/src/Infrastructure/BotSharp.Abstraction/Repositories/Filters/AgentFilter.cs
index fb2bfed2..e6060ae4 100644
--- a/src/Infrastructure/BotSharp.Abstraction/Repositories/Filters/AgentFilter.cs
+++ b/src/Infrastructure/BotSharp.Abstraction/Repositories/Filters/AgentFilter.cs
@@ -4,9 +4,15 @@ public class AgentFilter
{
public Pagination Pager { get; set; } = new Pagination();
public string? AgentName { get; set; }
+ public string? SimilarName { get; set; }
public bool? Disabled { get; set; }
public bool? Installed { get; set; }
public string? Type { get; set; }
public bool? IsPublic { get; set; }
public List? AgentIds { get; set; }
+
+ public static AgentFilter Empty()
+ {
+ return new AgentFilter();
+ }
}
diff --git a/src/Infrastructure/BotSharp.Abstraction/Repositories/Filters/AgentTaskFilter.cs b/src/Infrastructure/BotSharp.Abstraction/Repositories/Filters/AgentTaskFilter.cs
index 2d8ba477..46ab4621 100644
--- a/src/Infrastructure/BotSharp.Abstraction/Repositories/Filters/AgentTaskFilter.cs
+++ b/src/Infrastructure/BotSharp.Abstraction/Repositories/Filters/AgentTaskFilter.cs
@@ -5,4 +5,9 @@ public class AgentTaskFilter
public Pagination Pager { get; set; } = new Pagination();
public string? AgentId { get; set; }
public bool? Enabled { get; set; }
+
+ public static AgentTaskFilter Empty()
+ {
+ return new AgentTaskFilter();
+ }
}
diff --git a/src/Infrastructure/BotSharp.Abstraction/Repositories/Filters/ConversationFilter.cs b/src/Infrastructure/BotSharp.Abstraction/Repositories/Filters/ConversationFilter.cs
index 6ebedc5b..ab1a0a9f 100644
--- a/src/Infrastructure/BotSharp.Abstraction/Repositories/Filters/ConversationFilter.cs
+++ b/src/Infrastructure/BotSharp.Abstraction/Repositories/Filters/ConversationFilter.cs
@@ -25,4 +25,9 @@ public class ConversationFilter
public IEnumerable? States { get; set; } = [];
public IEnumerable? Tags { get; set; } = [];
+
+ public static ConversationFilter Empty()
+ {
+ return new ConversationFilter();
+ }
}
\ No newline at end of file
diff --git a/src/Infrastructure/BotSharp.Abstraction/Repositories/Filters/RoleFilter.cs b/src/Infrastructure/BotSharp.Abstraction/Repositories/Filters/RoleFilter.cs
new file mode 100644
index 00000000..35e2e1d3
--- /dev/null
+++ b/src/Infrastructure/BotSharp.Abstraction/Repositories/Filters/RoleFilter.cs
@@ -0,0 +1,18 @@
+using BotSharp.Abstraction.Users.Enums;
+
+namespace BotSharp.Abstraction.Repositories.Filters;
+
+public class RoleFilter
+{
+ [JsonPropertyName("names")]
+ public IEnumerable? Names { get; set; }
+
+ [JsonPropertyName("exclude_roles")]
+ public IEnumerable? ExcludeRoles { get; set; } = UserConstant.AdminRoles;
+
+
+ public static RoleFilter Empty()
+ {
+ return new RoleFilter();
+ }
+}
diff --git a/src/Infrastructure/BotSharp.Abstraction/Users/Models/UserFilter.cs b/src/Infrastructure/BotSharp.Abstraction/Repositories/Filters/UserFilter.cs
similarity index 69%
rename from src/Infrastructure/BotSharp.Abstraction/Users/Models/UserFilter.cs
rename to src/Infrastructure/BotSharp.Abstraction/Repositories/Filters/UserFilter.cs
index 73e1794d..4c0d259c 100644
--- a/src/Infrastructure/BotSharp.Abstraction/Users/Models/UserFilter.cs
+++ b/src/Infrastructure/BotSharp.Abstraction/Repositories/Filters/UserFilter.cs
@@ -1,4 +1,4 @@
-namespace BotSharp.Abstraction.Users.Models;
+namespace BotSharp.Abstraction.Repositories.Filters;
public class UserFilter : Pagination
{
@@ -14,6 +14,14 @@ public class UserFilter : Pagination
[JsonPropertyName("roles")]
public IEnumerable? Roles { get; set; }
+ [JsonPropertyName("types")]
+ public IEnumerable? Types { get; set; }
+
[JsonPropertyName("sources")]
public IEnumerable? Sources { get; set; }
+
+ public static UserFilter Empty()
+ {
+ return new UserFilter();
+ }
}
diff --git a/src/Infrastructure/BotSharp.Abstraction/Repositories/IBotSharpRepository.cs b/src/Infrastructure/BotSharp.Abstraction/Repositories/IBotSharpRepository.cs
index fb3fb187..f3859a3a 100644
--- a/src/Infrastructure/BotSharp.Abstraction/Repositories/IBotSharpRepository.cs
+++ b/src/Infrastructure/BotSharp.Abstraction/Repositories/IBotSharpRepository.cs
@@ -1,6 +1,7 @@
using BotSharp.Abstraction.Loggers.Models;
using BotSharp.Abstraction.Plugins.Models;
using BotSharp.Abstraction.Repositories.Filters;
+using BotSharp.Abstraction.Roles.Models;
using BotSharp.Abstraction.Shared;
using BotSharp.Abstraction.Tasks.Models;
using BotSharp.Abstraction.Translation.Models;
@@ -16,6 +17,13 @@ public interface IBotSharpRepository : IHaveServiceProvider
void SavePluginConfig(PluginConfig config);
#endregion
+ #region Role
+ bool RefreshRoles(IEnumerable roles) => throw new NotImplementedException();
+ IEnumerable GetRoles(RoleFilter filter) => throw new NotImplementedException();
+ Role? GetRoleDetails(string roleId, bool includeAgent = false) => throw new NotImplementedException();
+ bool UpdateRole(Role role, bool updateRoleAgents = false) => throw new NotImplementedException();
+ #endregion
+
#region User
User? GetUserByEmail(string email) => throw new NotImplementedException();
User? GetUserByPhone(string phone, string role = null, string regionCode = "CN") => throw new NotImplementedException();
@@ -34,7 +42,8 @@ public interface IBotSharpRepository : IHaveServiceProvider
void UpdateUserIsDisable(string userId, bool isDisable) => throw new NotImplementedException();
void UpdateUsersIsDisable(List userIds, bool isDisable) => throw new NotImplementedException();
PagedItems GetUsers(UserFilter filter) => throw new NotImplementedException();
- bool UpdateUser(User user, bool isUpdateUserAgents = false) => throw new NotImplementedException();
+ User? GetUserDetails(string userId, bool includeAgent = false) => throw new NotImplementedException();
+ bool UpdateUser(User user, bool updateUserAgents = false) => throw new NotImplementedException();
#endregion
#region Agent
diff --git a/src/Infrastructure/BotSharp.Abstraction/Roles/IRoleService.cs b/src/Infrastructure/BotSharp.Abstraction/Roles/IRoleService.cs
new file mode 100644
index 00000000..e1fa86db
--- /dev/null
+++ b/src/Infrastructure/BotSharp.Abstraction/Roles/IRoleService.cs
@@ -0,0 +1,13 @@
+using BotSharp.Abstraction.Repositories.Filters;
+using BotSharp.Abstraction.Roles.Models;
+
+namespace BotSharp.Abstraction.Roles;
+
+public interface IRoleService
+{
+ Task RefreshRoles();
+ Task> GetRoleOptions();
+ Task> GetRoles(RoleFilter filter);
+ Task GetRoleDetails(string roleId, bool includeAgent = false);
+ Task UpdateRole(Role role, bool isUpdateRoleAgents = false);
+}
diff --git a/src/Infrastructure/BotSharp.Abstraction/Roles/Models/Role.cs b/src/Infrastructure/BotSharp.Abstraction/Roles/Models/Role.cs
new file mode 100644
index 00000000..44d9e440
--- /dev/null
+++ b/src/Infrastructure/BotSharp.Abstraction/Roles/Models/Role.cs
@@ -0,0 +1,27 @@
+namespace BotSharp.Abstraction.Roles.Models;
+
+public class Role
+{
+ [JsonPropertyName("id")]
+ public string Id { get; set; }
+
+ [JsonPropertyName("name")]
+ public string Name { get; set; }
+
+ [JsonPropertyName("permissions")]
+ public IEnumerable Permissions { get; set; } = [];
+
+ [JsonIgnore]
+ public IEnumerable AgentActions { get; set; } = [];
+
+ [JsonPropertyName("updated_time")]
+ public DateTime UpdatedTime { get; set; }
+
+ [JsonPropertyName("created_time")]
+ public DateTime CreatedTime { get; set; }
+
+ public override string ToString()
+ {
+ return Name;
+ }
+}
diff --git a/src/Infrastructure/BotSharp.Abstraction/Roles/Models/RoleAgent.cs b/src/Infrastructure/BotSharp.Abstraction/Roles/Models/RoleAgent.cs
new file mode 100644
index 00000000..8b84591c
--- /dev/null
+++ b/src/Infrastructure/BotSharp.Abstraction/Roles/Models/RoleAgent.cs
@@ -0,0 +1,25 @@
+namespace BotSharp.Abstraction.Roles.Models;
+
+public class RoleAgent
+{
+ [JsonPropertyName("id")]
+ public string Id { get; set; } = string.Empty;
+
+ [JsonPropertyName("user_id")]
+ public string RoleId { get; set; } = string.Empty;
+
+ [JsonPropertyName("agent_id")]
+ public string AgentId { get; set; }
+
+ [JsonPropertyName("actions")]
+ public IEnumerable Actions { get; set; } = [];
+
+ [JsonIgnore]
+ public Agent? Agent { get; set; }
+
+ [JsonPropertyName("updated_time")]
+ public DateTime UpdatedTime { get; set; }
+
+ [JsonPropertyName("created_time")]
+ public DateTime CreatedTime { get; set; }
+}
diff --git a/src/Infrastructure/BotSharp.Abstraction/Roles/Models/RoleAgentAction.cs b/src/Infrastructure/BotSharp.Abstraction/Roles/Models/RoleAgentAction.cs
new file mode 100644
index 00000000..25cc0685
--- /dev/null
+++ b/src/Infrastructure/BotSharp.Abstraction/Roles/Models/RoleAgentAction.cs
@@ -0,0 +1,16 @@
+namespace BotSharp.Abstraction.Roles.Models;
+
+public class RoleAgentAction
+{
+ [JsonPropertyName("id")]
+ public string Id { get; set; }
+
+ [JsonPropertyName("agent_id")]
+ public string AgentId { get; set; }
+
+ [JsonIgnore]
+ public Agent? Agent { get; set; }
+
+ [JsonPropertyName("actions")]
+ public IEnumerable Actions { get; set; } = [];
+}
diff --git a/src/Infrastructure/BotSharp.Abstraction/Users/Enums/UserAction.cs b/src/Infrastructure/BotSharp.Abstraction/Users/Enums/UserAction.cs
index 4838e757..b565a260 100644
--- a/src/Infrastructure/BotSharp.Abstraction/Users/Enums/UserAction.cs
+++ b/src/Infrastructure/BotSharp.Abstraction/Users/Enums/UserAction.cs
@@ -4,4 +4,6 @@ public static class UserAction
{
public const string Edit = "edit";
public const string Chat = "chat";
+ public const string Train = "train";
+ public const string Evaluate = "evaluate";
}
diff --git a/src/Infrastructure/BotSharp.Abstraction/Users/Enums/UserRole.cs b/src/Infrastructure/BotSharp.Abstraction/Users/Enums/UserRole.cs
index 0bde3b08..59f7feff 100644
--- a/src/Infrastructure/BotSharp.Abstraction/Users/Enums/UserRole.cs
+++ b/src/Infrastructure/BotSharp.Abstraction/Users/Enums/UserRole.cs
@@ -2,21 +2,23 @@ namespace BotSharp.Abstraction.Users.Enums;
public class UserRole
{
+ public const string Root = "root";
+
///
/// Admin account
///
public const string Admin = "admin";
- ///
- /// Customer service representative (CSR)
- ///
- public const string CSR = "csr";
-
///
/// Authorized user
///
public const string User = "user";
+ ///
+ /// Customer service representative (CSR)
+ ///
+ public const string CSR = "csr";
+
///
/// Back office operations
///
@@ -33,6 +35,4 @@ public class UserRole
/// AI Assistant
///
public const string Assistant = "assistant";
-
- public const string Root = "root";
}
\ No newline at end of file
diff --git a/src/Infrastructure/BotSharp.Abstraction/Users/IUserService.cs b/src/Infrastructure/BotSharp.Abstraction/Users/IUserService.cs
index a5803600..cc67c1f7 100644
--- a/src/Infrastructure/BotSharp.Abstraction/Users/IUserService.cs
+++ b/src/Infrastructure/BotSharp.Abstraction/Users/IUserService.cs
@@ -1,3 +1,4 @@
+using BotSharp.Abstraction.Repositories.Filters;
using BotSharp.Abstraction.Users.Models;
using BotSharp.OpenAPI.ViewModels.Users;
@@ -7,7 +8,10 @@ public interface IUserService
{
Task GetUser(string id);
Task> GetUsers(UserFilter filter);
- Task UpdateUser(User model, bool isUpdateUserAgents = false);
+ Task GetUserDetails(string userId, bool includeAgent = false);
+ Task IsAdminUser(string userId);
+ Task GetUserAuthorizations(IEnumerable? agentIds = null);
+ Task UpdateUser(User user, bool isUpdateUserAgents = false);
Task CreateUser(User user);
Task ActiveUser(UserActivationModel model);
Task GetAffiliateToken(string authorization);
diff --git a/src/Infrastructure/BotSharp.Abstraction/Users/Models/UserAuthorization.cs b/src/Infrastructure/BotSharp.Abstraction/Users/Models/UserAuthorization.cs
new file mode 100644
index 00000000..56df6a7d
--- /dev/null
+++ b/src/Infrastructure/BotSharp.Abstraction/Users/Models/UserAuthorization.cs
@@ -0,0 +1,25 @@
+namespace BotSharp.Abstraction.Users.Models;
+
+public class UserAuthorization
+{
+ public bool IsAdmin { get; set; }
+ public IEnumerable Permissions { get; set; } = [];
+ public IEnumerable AgentActions { get; set; } = [];
+}
+
+
+public static class UserAuthorizationExtension
+{
+ public static bool IsAgentActionAllowed(this UserAuthorization auth, string agentId, string targetAction)
+ {
+ if (auth == null || string.IsNullOrEmpty(agentId)) return false;
+
+ if (auth.IsAdmin) return true;
+
+ var found = auth.AgentActions.FirstOrDefault(x => x.AgentId == agentId);
+ if (found == null) return false;
+
+ var actions = found.Actions ?? [];
+ return actions.Any(x => x == targetAction);
+ }
+}
\ No newline at end of file
diff --git a/src/Infrastructure/BotSharp.Core.SideCar/BotSharpSideCarPlugin.cs b/src/Infrastructure/BotSharp.Core.SideCar/BotSharpSideCarPlugin.cs
index efacd308..efef5201 100644
--- a/src/Infrastructure/BotSharp.Core.SideCar/BotSharpSideCarPlugin.cs
+++ b/src/Infrastructure/BotSharp.Core.SideCar/BotSharpSideCarPlugin.cs
@@ -8,7 +8,7 @@ namespace BotSharp.Core.SideCar;
public class BotSharpSideCarPlugin : IBotSharpPlugin
{
public string Id => "06e5a276-bba0-45af-9625-889267c341c9";
- public string Name => "Side car";
+ public string Name => "Side Car";
public string Description => "Provides side car for calling agent cluster in conversation";
public SettingsMeta Settings => new SettingsMeta("SideCar");
diff --git a/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.CreateAgent.cs b/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.CreateAgent.cs
index df10ac4b..7d4af3ac 100644
--- a/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.CreateAgent.cs
+++ b/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.CreateAgent.cs
@@ -25,8 +25,11 @@ public partial class AgentService
var agentSettings = _services.GetRequiredService();
var user = _db.GetUserById(_user.Id);
+ var userService = _services.GetRequiredService();
+ var auth = await userService.GetUserAuthorizations();
+
_db.BulkInsertAgents(new List { agentRecord });
- if (!UserConstant.AdminRoles.Contains(user.Role))
+ if (auth.IsAdmin || auth.Permissions.Contains(UserPermission.CreateAgent))
{
_db.BulkInsertUserAgents(new List
{
@@ -34,7 +37,7 @@ public partial class AgentService
{
UserId = user.Id,
AgentId = agentRecord.Id,
- Actions = new List { UserAction.Edit, UserAction.Chat },
+ Actions = new List { UserAction.Edit, UserAction.Train, UserAction.Evaluate, UserAction.Chat },
CreatedTime = DateTime.UtcNow,
UpdatedTime = DateTime.UtcNow
}
diff --git a/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.DeleteAgent.cs b/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.DeleteAgent.cs
index 8a05542e..6783bf91 100644
--- a/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.DeleteAgent.cs
+++ b/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.DeleteAgent.cs
@@ -1,4 +1,5 @@
using BotSharp.Abstraction.Users.Enums;
+using BotSharp.Abstraction.Users.Models;
namespace BotSharp.Core.Agents.Services;
@@ -6,11 +7,10 @@ public partial class AgentService
{
public async Task DeleteAgent(string id)
{
- var user = _db.GetUserById(_user.Id);
- var userAgents = await GetUserAgents(user?.Id);
- var found = userAgents?.FirstOrDefault(x => x.AgentId == id);
+ var userService = _services.GetRequiredService();
+ var auth = await userService.GetUserAuthorizations(new List { id });
- if (!UserConstant.AdminRoles.Contains(user?.Role) && (found?.Actions == null || !found.Actions.Contains(UserAction.Edit)))
+ if (!auth.IsAgentActionAllowed(id, UserAction.Edit))
{
return false;
}
diff --git a/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.RefreshAgents.cs b/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.RefreshAgents.cs
index 61861aca..f0838ce5 100644
--- a/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.RefreshAgents.cs
+++ b/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.RefreshAgents.cs
@@ -1,5 +1,4 @@
using BotSharp.Abstraction.Repositories.Enums;
-using BotSharp.Abstraction.Users.Enums;
using System.IO;
namespace BotSharp.Core.Agents.Services;
@@ -17,8 +16,9 @@ public partial class AgentService
return refreshResult;
}
- var user = _db.GetUserById(_user.Id);
- if (!UserConstant.AdminRoles.Contains(user.Role))
+ var userService = _services.GetRequiredService();
+ var isValid = await userService.IsAdminUser(_user.Id);
+ if (!isValid)
{
return "Unauthorized user.";
}
diff --git a/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.UpdateAgent.cs b/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.UpdateAgent.cs
index 17aa4aa7..18afb27f 100644
--- a/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.UpdateAgent.cs
+++ b/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.UpdateAgent.cs
@@ -1,6 +1,7 @@
using BotSharp.Abstraction.Repositories.Enums;
using BotSharp.Abstraction.Routing.Models;
using BotSharp.Abstraction.Users.Enums;
+using BotSharp.Abstraction.Users.Models;
using System.IO;
namespace BotSharp.Core.Agents.Services;
@@ -12,12 +13,10 @@ public partial class AgentService
if (agent == null || string.IsNullOrEmpty(agent.Id)) return;
var userService = _services.GetRequiredService();
- var user = await userService.GetUser(_user.Id);
+ var auth = await userService.GetUserAuthorizations(new List { agent.Id });
+ var allowEdit = auth.IsAgentActionAllowed(agent.Id, UserAction.Edit);
- var userAgents = await GetUserAgents(user.Id);
- var found = userAgents?.FirstOrDefault(x => x.AgentId == agent.Id);
-
- if (!UserConstant.AdminRoles.Contains(user?.Role) && (found?.Actions == null || found.Actions.Contains(UserAction.Edit)))
+ if (!allowEdit)
{
return;
}
diff --git a/src/Infrastructure/BotSharp.Core/BotSharpCoreExtensions.cs b/src/Infrastructure/BotSharp.Core/BotSharpCoreExtensions.cs
index 862ef899..a60fb4fd 100644
--- a/src/Infrastructure/BotSharp.Core/BotSharpCoreExtensions.cs
+++ b/src/Infrastructure/BotSharp.Core/BotSharpCoreExtensions.cs
@@ -11,6 +11,7 @@ using BotSharp.Abstraction.Infrastructures;
using BotSharp.Core.Processors;
using StackExchange.Redis;
using BotSharp.Core.Infrastructures.Events;
+using BotSharp.Core.Roles.Services;
namespace BotSharp.Core;
@@ -25,6 +26,7 @@ public static class BotSharpCoreExtensions
services.AddSingleton();
services.AddScoped();
+ services.AddScoped();
services.AddScoped();
services.AddScoped();
diff --git a/src/Infrastructure/BotSharp.Core/Plugins/PluginLoader.cs b/src/Infrastructure/BotSharp.Core/Plugins/PluginLoader.cs
index 05efe39d..cf25dffd 100644
--- a/src/Infrastructure/BotSharp.Core/Plugins/PluginLoader.cs
+++ b/src/Infrastructure/BotSharp.Core/Plugins/PluginLoader.cs
@@ -4,6 +4,7 @@ using Microsoft.Extensions.Configuration;
using System.Drawing;
using System.IO;
using System.Reflection;
+using System.Text.RegularExpressions;
using System.Xml;
namespace BotSharp.Core.Plugins;
@@ -132,6 +133,12 @@ public class PluginLoader
plugins = plugins.Where(x => filter.Names.Any(n => x.Name.IsEqualTo(n))).ToList();
}
+ if (!string.IsNullOrEmpty(filter.SimilarName))
+ {
+ var regex = new Regex(filter.SimilarName, RegexOptions.Compiled | RegexOptions.IgnoreCase);
+ plugins = plugins.Where(x => regex.IsMatch(x.Name)).ToList();
+ }
+
return new PagedItems
{
Items = plugins.Skip(pager.Offset).Take(pager.Size),
diff --git a/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Agent.cs b/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Agent.cs
index 59da45a5..7f545527 100644
--- a/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Agent.cs
+++ b/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Agent.cs
@@ -1,8 +1,6 @@
-using BotSharp.Abstraction.Agents.Models;
using BotSharp.Abstraction.Routing.Models;
-using BotSharp.Abstraction.Users.Models;
-using Microsoft.Extensions.Logging;
using System.IO;
+using System.Text.RegularExpressions;
namespace BotSharp.Core.Repository
{
@@ -65,6 +63,8 @@ namespace BotSharp.Core.Repository
default:
break;
}
+
+ _agents = [];
}
#region Update Agent Fields
@@ -358,12 +358,23 @@ namespace BotSharp.Core.Repository
public List GetAgents(AgentFilter filter)
{
+ if (filter == null)
+ {
+ filter = AgentFilter.Empty();
+ }
+
var query = Agents;
if (!string.IsNullOrEmpty(filter.AgentName))
{
query = query.Where(x => x.Name.ToLower() == filter.AgentName.ToLower());
}
+ if (!string.IsNullOrEmpty(filter.SimilarName))
+ {
+ var regex = new Regex(filter.SimilarName, RegexOptions.Compiled | RegexOptions.IgnoreCase);
+ query = query.Where(x => regex.IsMatch(x.Name));
+ }
+
if (filter.Disabled.HasValue)
{
query = query.Where(x => x.Disabled == filter.Disabled);
@@ -476,7 +487,8 @@ namespace BotSharp.Core.Repository
File.WriteAllText(instFile, agent.Instruction);
}
}
- Reset();
+
+ ResetLocalAgents();
}
public void BulkInsertUserAgents(List userAgents)
@@ -509,7 +521,7 @@ namespace BotSharp.Core.Repository
Thread.Sleep(50);
}
- Reset();
+ ResetLocalAgents();
}
public bool DeleteAgents()
@@ -526,7 +538,7 @@ namespace BotSharp.Core.Repository
var agentDir = GetAgentDataDir(agentId);
if (string.IsNullOrEmpty(agentDir)) return false;
- // Delete agent user relationships
+ // Delete user agents
var usersDir = Path.Combine(_dbSettings.FileRepository, USERS_FOLDER);
if (Directory.Exists(usersDir))
{
@@ -544,9 +556,27 @@ namespace BotSharp.Core.Repository
}
}
+ // Delete role agents
+ var rolesDir = Path.Combine(_dbSettings.FileRepository, ROLES_FOLDER);
+ if (Directory.Exists(rolesDir))
+ {
+ foreach (var roleDir in Directory.GetDirectories(rolesDir))
+ {
+ var roleAgentFile = Directory.GetFiles(roleDir).FirstOrDefault(x => Path.GetFileName(x) == ROLE_AGENT_FILE);
+ if (string.IsNullOrEmpty(roleAgentFile)) continue;
+
+ var text = File.ReadAllText(roleAgentFile);
+ var roleAgents = JsonSerializer.Deserialize>(text, _options);
+ if (roleAgents.IsNullOrEmpty()) continue;
+
+ roleAgents = roleAgents?.Where(x => x.AgentId != agentId)?.ToList() ?? [];
+ File.WriteAllText(roleAgentFile, JsonSerializer.Serialize(roleAgents, _options));
+ }
+ }
+
// Delete agent folder
Directory.Delete(agentDir, true);
- Reset();
+ ResetLocalAgents();
return true;
}
catch
@@ -555,10 +585,11 @@ namespace BotSharp.Core.Repository
}
}
- private void Reset()
+ private void ResetLocalAgents()
{
_agents = [];
_userAgents = [];
+ _roleAgents = [];
}
}
}
diff --git a/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.AgentTask.cs b/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.AgentTask.cs
index 776cf137..f855544b 100644
--- a/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.AgentTask.cs
+++ b/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.AgentTask.cs
@@ -8,6 +8,11 @@ public partial class FileRepository
#region Task
public PagedItems GetAgentTasks(AgentTaskFilter filter)
{
+ if (filter == null)
+ {
+ filter = AgentTaskFilter.Empty();
+ }
+
var tasks = new List();
var pager = filter.Pager ?? new Pagination();
var skipCount = 0;
diff --git a/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Conversation.cs b/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Conversation.cs
index 28d0a6cc..fd13936c 100644
--- a/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Conversation.cs
+++ b/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Conversation.cs
@@ -54,6 +54,7 @@ namespace BotSharp.Core.Repository
Directory.Delete(convDir, true);
}
+
return true;
}
@@ -322,6 +323,11 @@ namespace BotSharp.Core.Repository
public PagedItems GetConversations(ConversationFilter filter)
{
+ if (filter == null)
+ {
+ filter = ConversationFilter.Empty();
+ }
+
var records = new List();
var dir = Path.Combine(_dbSettings.FileRepository, _conversationSettings.DataDir);
var pager = filter?.Pager ?? new Pagination();
diff --git a/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Role.cs b/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Role.cs
new file mode 100644
index 00000000..c797ce5a
--- /dev/null
+++ b/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Role.cs
@@ -0,0 +1,143 @@
+using System.IO;
+
+namespace BotSharp.Core.Repository;
+
+public partial class FileRepository
+{
+ public bool RefreshRoles(IEnumerable roles)
+ {
+ if (roles.IsNullOrEmpty()) return false;
+
+ var validRoles = roles.Where(x => !string.IsNullOrWhiteSpace(x.Id)
+ && !string.IsNullOrWhiteSpace(x.Name)).ToList();
+ if (validRoles.IsNullOrEmpty()) return false;
+
+ var baseDir = Path.Combine(_dbSettings.FileRepository, ROLES_FOLDER);
+ if (Directory.Exists(baseDir))
+ {
+ Directory.Delete(baseDir, true);
+ }
+
+ Directory.CreateDirectory(baseDir);
+
+ foreach (var role in validRoles)
+ {
+ var dir = Path.Combine(baseDir, role.Id);
+ Directory.CreateDirectory(dir);
+ Thread.Sleep(50);
+ var roleFile = Path.Combine(dir, ROLE_FILE);
+ role.CreatedTime = DateTime.UtcNow;
+ role.UpdatedTime = DateTime.UtcNow;
+ File.WriteAllText(roleFile, JsonSerializer.Serialize(role, _options));
+ }
+
+ return true;
+ }
+
+ public IEnumerable GetRoles(RoleFilter filter)
+ {
+ var roles = Roles;
+ if (filter == null)
+ {
+ filter = RoleFilter.Empty();
+ }
+
+ // Apply filters
+ if (!filter.Names.IsNullOrEmpty())
+ {
+ roles = roles.Where(x => filter.Names.Contains(x.Name));
+ }
+
+ if (!filter.ExcludeRoles.IsNullOrEmpty())
+ {
+ roles = roles.Where(x => !filter.ExcludeRoles.Contains(x.Name));
+ }
+
+ return roles.ToList();
+ }
+
+ public Role? GetRoleDetails(string roleId, bool includeAgent = false)
+ {
+ if (string.IsNullOrWhiteSpace(roleId)) return null;
+
+ var role = Roles.FirstOrDefault(x => x.Id == roleId);
+ if (role == null) return null;
+
+ var agentActions = new List();
+ var roleAgents = RoleAgents?.Where(x => x.RoleId == roleId)?.ToList() ?? [];
+
+ if (!includeAgent)
+ {
+ agentActions = roleAgents.Select(x => new RoleAgentAction
+ {
+ Id = x.Id,
+ AgentId = x.AgentId,
+ Actions = x.Actions
+ }).ToList();
+ role.AgentActions = agentActions;
+ return role;
+ }
+
+ var agentIds = roleAgents.Select(x => x.AgentId).Distinct().ToList();
+ if (!agentIds.IsNullOrEmpty())
+ {
+ var agents = GetAgents(new AgentFilter { AgentIds = agentIds });
+
+ foreach (var item in roleAgents)
+ {
+ var found = agents.FirstOrDefault(x => x.Id == item.AgentId);
+ if (found == null) continue;
+
+ agentActions.Add(new RoleAgentAction
+ {
+ Id = item.Id,
+ AgentId = found.Id,
+ Agent = found,
+ Actions = item.Actions
+ });
+ }
+ }
+
+ role.AgentActions = agentActions;
+ return role;
+ }
+
+ public bool UpdateRole(Role role, bool updateRoleAgents = false)
+ {
+ if (string.IsNullOrEmpty(role?.Id) || string.IsNullOrEmpty(role?.Name))
+ {
+ return false;
+ }
+
+ var dir = Path.Combine(_dbSettings.FileRepository, ROLES_FOLDER, role.Id);
+ if (!Directory.Exists(dir))
+ {
+ Directory.CreateDirectory(dir);
+ }
+
+ var roleFile = Path.Combine(dir, ROLE_FILE);
+ role.CreatedTime = DateTime.UtcNow;
+ role.UpdatedTime = DateTime.UtcNow;
+ File.WriteAllText(roleFile, JsonSerializer.Serialize(role, _options));
+
+ if (updateRoleAgents)
+ {
+ var roleAgents = role.AgentActions?.Select(x => new RoleAgent
+ {
+ Id = !string.IsNullOrEmpty(x.Id) ? x.Id : Guid.NewGuid().ToString(),
+ RoleId = role.Id,
+ AgentId = x.AgentId,
+ Actions = x.Actions ?? [],
+ CreatedTime = DateTime.UtcNow,
+ UpdatedTime = DateTime.UtcNow
+ })?.ToList() ?? [];
+
+ var roleAgentFile = Path.Combine(dir, ROLE_AGENT_FILE);
+ File.WriteAllText(roleAgentFile, JsonSerializer.Serialize(roleAgents, _options));
+ _roleAgents = [];
+ }
+
+ _roles = [];
+ return true;
+ }
+}
diff --git a/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.User.cs b/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.User.cs
index 80ccacc4..9600bbc8 100644
--- a/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.User.cs
+++ b/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.User.cs
@@ -1,7 +1,5 @@
-using BotSharp.Abstraction.Agents.Models;
using BotSharp.Abstraction.Users.Enums;
using BotSharp.Abstraction.Users.Models;
-using System;
using System.IO;
namespace BotSharp.Core.Repository;
@@ -73,6 +71,11 @@ public partial class FileRepository
public PagedItems GetUsers(UserFilter filter)
{
+ if (filter == null)
+ {
+ filter = UserFilter.Empty();
+ }
+
var users = Users;
// Apply filters
@@ -92,39 +95,15 @@ public partial class FileRepository
{
users = users.Where(x => filter.Roles.Contains(x.Role));
}
+ if (!filter.Types.IsNullOrEmpty())
+ {
+ users = users.Where(x => filter.Types.Contains(x.Type));
+ }
if (!filter.Sources.IsNullOrEmpty())
{
users = users.Where(x => filter.Sources.Contains(x.Source));
}
- // Get user agents
- var userIds = users.Select(x => x.Id).ToList();
- var userAgents = UserAgents.Where(x => userIds.Contains(x.UserId)).ToList();
- var agentIds = userAgents?.Select(x => x.AgentId)?.Distinct()?.ToList() ?? [];
-
- if (!agentIds.IsNullOrEmpty())
- {
- var agents = GetAgents(new AgentFilter { AgentIds = agentIds });
- foreach (var item in userAgents)
- {
- item.Agent = agents.FirstOrDefault(x => x.Id == item.AgentId);
- }
-
- foreach (var user in users)
- {
- var found = userAgents.Where(x => x.UserId == user.Id).ToList();
- if (found.IsNullOrEmpty()) continue;
-
- user.AgentActions = found.Select(x => new UserAgentAction
- {
- Id = x.Id,
- AgentId = x.AgentId,
- Agent = x.Agent,
- Actions = x.Actions
- });
- }
- }
-
return new PagedItems
{
Items = users.OrderByDescending(x => x.CreatedTime).Skip(filter.Offset).Take(filter.Size),
@@ -132,7 +111,53 @@ public partial class FileRepository
};
}
- public bool UpdateUser(User user, bool isUpdateUserAgents = false)
+ public User? GetUserDetails(string userId, bool includeAgent = false)
+ {
+ if (string.IsNullOrWhiteSpace(userId)) return null;
+
+ var user = Users.FirstOrDefault(x => x.Id == userId || x.ExternalId == userId);
+ if (user == null) return null;
+
+ var agentActions = new List();
+ var userAgents = UserAgents?.Where(x => x.UserId == userId)?.ToList() ?? [];
+
+ if (!includeAgent)
+ {
+ agentActions = userAgents.Select(x => new UserAgentAction
+ {
+ Id = x.Id,
+ AgentId = x.AgentId,
+ Actions = x.Actions
+ }).ToList();
+ user.AgentActions = agentActions;
+ return user;
+ }
+
+ var agentIds = userAgents.Select(x => x.AgentId)?.Distinct().ToList();
+ if (!agentIds.IsNullOrEmpty())
+ {
+ var agents = GetAgents(new AgentFilter { AgentIds = agentIds });
+
+ foreach (var item in userAgents)
+ {
+ var found = agents.FirstOrDefault(x => x.Id == item.AgentId);
+ if (found == null) continue;
+
+ agentActions.Add(new UserAgentAction
+ {
+ Id = item.Id,
+ AgentId = found.Id,
+ Agent = found,
+ Actions = item.Actions ?? []
+ });
+ }
+ }
+
+ user.AgentActions = agentActions;
+ return user;
+ }
+
+ public bool UpdateUser(User user, bool updateUserAgents = false)
{
if (string.IsNullOrEmpty(user?.Id)) return false;
@@ -146,7 +171,7 @@ public partial class FileRepository
user.UpdatedTime = DateTime.UtcNow;
File.WriteAllText(userFile, JsonSerializer.Serialize(user, _options));
- if (isUpdateUserAgents)
+ if (updateUserAgents)
{
var userAgents = user.AgentActions?.Select(x => new UserAgent
{
@@ -160,10 +185,10 @@ public partial class FileRepository
var userAgentFile = Path.Combine(dir, USER_AGENT_FILE);
File.WriteAllText(userAgentFile, JsonSerializer.Serialize(userAgents, _options));
+ _userAgents = [];
}
_users = [];
- _userAgents = [];
return true;
}
}
diff --git a/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.cs b/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.cs
index f3e1fddf..0edcb699 100644
--- a/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.cs
+++ b/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.cs
@@ -22,30 +22,38 @@ public partial class FileRepository : IBotSharpRepository
private const string AGENT_FILE = "agent.json";
private const string AGENT_INSTRUCTION_FILE = "instruction";
private const string AGENT_SAMPLES_FILE = "samples.txt";
- private const string USER_FILE = "user.json";
- private const string USER_AGENT_FILE = "agents.json";
- private const string CONVERSATION_FILE = "conversation.json";
- private const string STATS_FILE = "stats.json";
- private const string DIALOG_FILE = "dialogs.json";
- private const string STATE_FILE = "state.json";
- private const string BREAKPOINT_FILE = "breakpoint.json";
- private const string EXECUTION_LOG_FILE = "execution.log";
- private const string PLUGIN_CONFIG_FILE = "config.json";
- private const string AGENT_TASK_PREFIX = "#metadata";
- private const string AGENT_TASK_SUFFIX = "/metadata";
- private const string TRANSLATION_MEMORY_FILE = "memory.json";
private const string AGENT_INSTRUCTIONS_FOLDER = "instructions";
private const string AGENT_FUNCTIONS_FOLDER = "functions";
private const string AGENT_TEMPLATES_FOLDER = "templates";
private const string AGENT_RESPONSES_FOLDER = "responses";
private const string AGENT_TASKS_FOLDER = "tasks";
+ private const string AGENT_TASK_PREFIX = "#metadata";
+ private const string AGENT_TASK_SUFFIX = "/metadata";
+
+ private const string CONVERSATION_FILE = "conversation.json";
+ private const string DIALOG_FILE = "dialogs.json";
+ private const string STATE_FILE = "state.json";
+ private const string BREAKPOINT_FILE = "breakpoint.json";
+ private const string TRANSLATION_MEMORY_FILE = "memory.json";
+
private const string USERS_FOLDER = "users";
+ private const string USER_FILE = "user.json";
+ private const string USER_AGENT_FILE = "agents.json";
+
+ private const string ROLES_FOLDER = "roles";
+ private const string ROLE_FILE = "role.json";
+ private const string ROLE_AGENT_FILE = "agents.json";
+
private const string KNOWLEDGE_FOLDER = "knowledgebase";
private const string VECTOR_FOLDER = "vector";
private const string COLLECTION_CONFIG_FILE = "collection-config.json";
private const string KNOWLEDGE_DOC_FOLDER = "document";
private const string KNOWLEDGE_DOC_META_FILE = "meta.json";
+ private const string EXECUTION_LOG_FILE = "execution.log";
+ private const string PLUGIN_CONFIG_FILE = "config.json";
+ private const string STATS_FILE = "stats.json";
+
public FileRepository(
IServiceProvider services,
BotSharpDatabaseSettings dbSettings,
@@ -73,12 +81,68 @@ public partial class FileRepository : IBotSharpRepository
_dbSettings.FileRepository = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, _dbSettings.FileRepository);
}
+ private List _roles = new List();
private List _users = new List();
private List _agents = new List();
+ private List _roleAgents = new List();
private List _userAgents = new List();
private List _conversations = new List();
private PluginConfig? _pluginConfig = null;
+ private IQueryable Roles
+ {
+ get
+ {
+ if (!_roles.IsNullOrEmpty())
+ {
+ return _roles.AsQueryable();
+ }
+
+ var dir = Path.Combine(_dbSettings.FileRepository, ROLES_FOLDER);
+ _roles = new List();
+ if (Directory.Exists(dir))
+ {
+ foreach (var d in Directory.GetDirectories(dir))
+ {
+ var roleFile = Path.Combine(d, ROLE_FILE);
+ if (!Directory.Exists(d) || !File.Exists(roleFile))
+ continue;
+
+ var json = File.ReadAllText(roleFile);
+ _roles.Add(JsonSerializer.Deserialize(json, _options));
+ }
+ }
+ return _roles.AsQueryable();
+ }
+ }
+
+ private IQueryable RoleAgents
+ {
+ get
+ {
+ if (!_roleAgents.IsNullOrEmpty())
+ {
+ return _roleAgents.AsQueryable();
+ }
+
+ var dir = Path.Combine(_dbSettings.FileRepository, ROLES_FOLDER);
+ _roleAgents = new List();
+ if (Directory.Exists(dir))
+ {
+ foreach (var d in Directory.GetDirectories(dir))
+ {
+ var file = Path.Combine(d, ROLE_AGENT_FILE);
+ if (!Directory.Exists(d) || !File.Exists(file))
+ continue;
+
+ var json = File.ReadAllText(file);
+ _roleAgents.AddRange(JsonSerializer.Deserialize>(json, _options));
+ }
+ }
+ return _roleAgents.AsQueryable();
+ }
+ }
+
private IQueryable Users
{
get
diff --git a/src/Infrastructure/BotSharp.Core/Roles/Services/RoleService.cs b/src/Infrastructure/BotSharp.Core/Roles/Services/RoleService.cs
new file mode 100644
index 00000000..8cd7935b
--- /dev/null
+++ b/src/Infrastructure/BotSharp.Core/Roles/Services/RoleService.cs
@@ -0,0 +1,65 @@
+using BotSharp.Abstraction.Users.Enums;
+using System.Reflection;
+
+namespace BotSharp.Core.Roles.Services;
+
+public class RoleService : IRoleService
+{
+ private readonly IServiceProvider _services;
+ private readonly ILogger _logger;
+
+ public RoleService(
+ IServiceProvider services,
+ ILogger logger)
+ {
+ _services = services;
+ _logger = logger;
+ }
+
+ public async Task RefreshRoles()
+ {
+ var allRoles = await GetRoleOptions();
+ var roles = allRoles.Select(x => new Role { Id = Guid.NewGuid().ToString(), Name = x }).ToList();
+
+ var db = _services.GetRequiredService();
+ return db.RefreshRoles(roles);
+ }
+
+ public async Task> GetRoleOptions()
+ {
+ var fields = typeof(UserRole).GetFields(BindingFlags.Public | BindingFlags.Static)
+ .Where(x => x.IsLiteral && !x.IsInitOnly).ToList();
+
+ return fields.Select(x => x.GetValue(null)?.ToString())
+ .Where(x => !string.IsNullOrWhiteSpace(x))
+ .Distinct()
+ .ToList();
+ }
+
+ public async Task> GetRoles(RoleFilter filter)
+ {
+ var db = _services.GetRequiredService();
+ var roles = db.GetRoles(filter);
+ return roles;
+ }
+
+ public async Task GetRoleDetails(string roleId, bool includeAgent = false)
+ {
+ var db = _services.GetRequiredService();
+ var role = db.GetRoleDetails(roleId, includeAgent);
+ return role;
+ }
+
+ public async Task UpdateRole(Role role, bool isUpdateRoleAgents = false)
+ {
+ if (role == null) return false;
+
+ if (string.IsNullOrEmpty(role.Id))
+ {
+ role.Id = Guid.NewGuid().ToString();
+ }
+
+ var db = _services.GetRequiredService();
+ return db.UpdateRole(role, isUpdateRoleAgents);
+ }
+}
diff --git a/src/Infrastructure/BotSharp.Core/Users/Services/UserService.cs b/src/Infrastructure/BotSharp.Core/Users/Services/UserService.cs
index d152cad4..9e74d961 100644
--- a/src/Infrastructure/BotSharp.Core/Users/Services/UserService.cs
+++ b/src/Infrastructure/BotSharp.Core/Users/Services/UserService.cs
@@ -407,10 +407,63 @@ public class UserService : IUserService
return users;
}
- public async Task UpdateUser(User model, bool isUpdateUserAgents = false)
+ public async Task IsAdminUser(string userId)
{
var db = _services.GetRequiredService();
- return db.UpdateUser(model, isUpdateUserAgents);
+ var user = db.GetUserById(userId);
+ return user != null && UserConstant.AdminRoles.Contains(user.Role);
+ }
+
+ public async Task GetUserAuthorizations(IEnumerable? agentIds = null)
+ {
+ var db = _services.GetRequiredService();
+ var user = db.GetUserById(_user.Id);
+ var auth = new UserAuthorization();
+
+ if (user == null) return auth;
+
+ auth.IsAdmin = UserConstant.AdminRoles.Contains(user.Role);
+
+ var role = db.GetRoles(new RoleFilter { Names = [user.Role] }).FirstOrDefault();
+ var permissions = user.Permissions?.Any() == true ? user.Permissions : role?.Permissions ?? [];
+ auth.Permissions = permissions;
+
+ if (agentIds == null || !agentIds.Any())
+ {
+ return auth;
+ }
+
+ var userAgents = db.GetUserDetails(user.Id)?.AgentActions?
+ .Where(x => agentIds.Contains(x.AgentId) && x.Actions.Any())?.Select(x => new UserAgent
+ {
+ AgentId = x.AgentId,
+ Actions = x.Actions
+ }).ToList() ?? [];
+
+ var userAgentIds = userAgents.Select(x => x.AgentId).ToList();
+ var roleAgents = db.GetRoleDetails(role?.Id)?.AgentActions?
+ .Where(x => !userAgentIds.Contains(x.AgentId))?.Select(x => new UserAgent
+ {
+ AgentId = x.AgentId,
+ Actions = x.Actions
+ })?.ToList() ?? [];
+
+ auth.AgentActions = userAgents.Concat(roleAgents);
+ return auth;
+ }
+
+ public async Task GetUserDetails(string userId, bool includeAgent = false)
+ {
+ var db = _services.GetRequiredService();
+ return db.GetUserDetails(userId, includeAgent);
+ }
+
+ public async Task UpdateUser(User user, bool isUpdateUserAgents = false)
+ {
+ if (user == null) return false;
+
+ var db = _services.GetRequiredService();
+ return db.UpdateUser(user, isUpdateUserAgents);
}
public async Task ActiveUser(UserActivationModel model)
diff --git a/src/Infrastructure/BotSharp.Core/Using.cs b/src/Infrastructure/BotSharp.Core/Using.cs
index 9e9177ba..f68cba9b 100644
--- a/src/Infrastructure/BotSharp.Core/Using.cs
+++ b/src/Infrastructure/BotSharp.Core/Using.cs
@@ -16,6 +16,8 @@ global using BotSharp.Abstraction.Agents;
global using BotSharp.Abstraction.Conversations;
global using BotSharp.Abstraction.Knowledges;
global using BotSharp.Abstraction.Users;
+global using BotSharp.Abstraction.Roles;
+global using BotSharp.Abstraction.Roles.Models;
global using BotSharp.Abstraction.Utilities;
global using BotSharp.Abstraction.Conversations.Models;
global using BotSharp.Abstraction.Agents.Settings;
diff --git a/src/Infrastructure/BotSharp.OpenAPI/Controllers/AgentController.cs b/src/Infrastructure/BotSharp.OpenAPI/Controllers/AgentController.cs
index 54c9b75e..5ce68c2a 100644
--- a/src/Infrastructure/BotSharp.OpenAPI/Controllers/AgentController.cs
+++ b/src/Infrastructure/BotSharp.OpenAPI/Controllers/AgentController.cs
@@ -1,4 +1,3 @@
-using BotSharp.Abstraction.Agents.Models;
using BotSharp.Abstraction.Users.Enums;
namespace BotSharp.OpenAPI.Controllers;
@@ -58,20 +57,13 @@ public class AgentController : ControllerBase
rule.RedirectToAgentName = found.Name;
}
- var editable = true;
- var chatable = true;
var userService = _services.GetRequiredService();
- var user = await userService.GetUser(_user.Id);
- if (!UserConstant.AdminRoles.Contains(user?.Role))
- {
- var userAgents = await _agentService.GetUserAgents(user?.Id);
- var actions = userAgents?.FirstOrDefault(x => x.AgentId == targetAgent.Id)?.Actions ?? [];
- editable = actions.Contains(UserAction.Edit);
- chatable = actions.Contains(UserAction.Chat);
- }
+ var auth = await userService.GetUserAuthorizations(new List { targetAgent.Id });
- targetAgent.Editable = editable;
- targetAgent.Chatable = chatable;
+ targetAgent.Editable = auth.IsAgentActionAllowed(targetAgent.Id, UserAction.Edit);
+ targetAgent.Chatable = auth.IsAgentActionAllowed(targetAgent.Id, UserAction.Chat);
+ targetAgent.Trainable = auth.IsAgentActionAllowed(targetAgent.Id, UserAction.Train);
+ targetAgent.Evaluable = auth.IsAgentActionAllowed(targetAgent.Id, UserAction.Evaluate);
return targetAgent;
}
@@ -94,27 +86,14 @@ public class AgentController : ControllerBase
};
}
- var userAgents = new List();
- var user = await userService.GetUser(_user.Id);
- if (!UserConstant.AdminRoles.Contains(user.Role))
- {
- userAgents = await _agentService.GetUserAgents(user.Id);
- }
-
+ var auth = await userService.GetUserAuthorizations(pagedAgents.Items.Select(x => x.Id));
agents = pagedAgents?.Items?.Select(x =>
{
- var chatable = true;
- var editable = true;
- if (!UserConstant.AdminRoles.Contains(user.Role))
- {
- var actions = userAgents.FirstOrDefault(a => a.AgentId == x.Id)?.Actions ?? [];
- chatable = actions.Contains(UserAction.Chat);
- editable = actions.Contains(UserAction.Edit);
- }
-
var model = AgentViewModel.FromAgent(x);
- model.Editable = editable;
- model.Chatable = chatable;
+ model.Editable = auth.IsAgentActionAllowed(x.Id, UserAction.Edit);
+ model.Chatable = auth.IsAgentActionAllowed(x.Id, UserAction.Chat);
+ model.Trainable = auth.IsAgentActionAllowed(x.Id, UserAction.Train);
+ model.Evaluable = auth.IsAgentActionAllowed(x.Id, UserAction.Evaluate);
return model;
})?.ToList() ?? [];
diff --git a/src/Infrastructure/BotSharp.OpenAPI/Controllers/KnowledgeBaseController.cs b/src/Infrastructure/BotSharp.OpenAPI/Controllers/KnowledgeBaseController.cs
index 3ead41b0..5a1a6bda 100644
--- a/src/Infrastructure/BotSharp.OpenAPI/Controllers/KnowledgeBaseController.cs
+++ b/src/Infrastructure/BotSharp.OpenAPI/Controllers/KnowledgeBaseController.cs
@@ -120,12 +120,13 @@ public class KnowledgeBaseController : ControllerBase
[HttpPost("/knowledge/document/{collection}/upload")]
public async Task UploadKnowledgeDocuments([FromRoute] string collection, [FromBody] VectorKnowledgeUploadRequest request)
{
- var response = await _knowledgeService.UploadDocumentsToKnowledge(collection, request.Files);
+ var response = await _knowledgeService.UploadDocumentsToKnowledge(collection, request.Files, request.ChunkOption);
return response;
}
[HttpPost("/knowledge/document/{collection}/form-upload")]
- public async Task UploadKnowledgeDocuments([FromRoute] string collection, [FromForm] IEnumerable files)
+ public async Task UploadKnowledgeDocuments([FromRoute] string collection,
+ [FromForm] IEnumerable files, [FromForm] ChunkOption? option = null)
{
if (files.IsNullOrEmpty())
{
@@ -143,7 +144,7 @@ public class KnowledgeBaseController : ControllerBase
});
}
- var response = await _knowledgeService.UploadDocumentsToKnowledge(collection, docs);
+ var response = await _knowledgeService.UploadDocumentsToKnowledge(collection, docs, option);
return response;
}
diff --git a/src/Infrastructure/BotSharp.OpenAPI/Controllers/PluginController.cs b/src/Infrastructure/BotSharp.OpenAPI/Controllers/PluginController.cs
index e4ec4aa8..499ebdf7 100644
--- a/src/Infrastructure/BotSharp.OpenAPI/Controllers/PluginController.cs
+++ b/src/Infrastructure/BotSharp.OpenAPI/Controllers/PluginController.cs
@@ -22,9 +22,8 @@ public class PluginController : ControllerBase
[HttpGet("/plugins")]
public async Task> GetPlugins([FromQuery] PluginFilter filter)
{
- var userService = _services.GetRequiredService();
- var user = await userService.GetUser(_user.Id);
- if (!UserConstant.AdminRoles.Contains(user?.Role))
+ var isValid = await IsValidUser();
+ if (!isValid)
{
return new PagedItems();
}
@@ -55,7 +54,11 @@ public class PluginController : ControllerBase
{
Roles = new List { UserRole.Root, UserRole.Admin }
},
- new PluginMenuDef("Users", link: "page/users", icon: "bx bx-user", weight: 33)
+ new PluginMenuDef("Roles", link: "page/roles", icon: "bx bx-group", weight: 33)
+ {
+ Roles = new List { UserRole.Root, UserRole.Admin }
+ },
+ new PluginMenuDef("Users", link: "page/users", icon: "bx bx-user", weight: 34)
{
Roles = new List { UserRole.Root, UserRole.Admin }
}
@@ -91,4 +94,10 @@ public class PluginController : ControllerBase
var loader = _services.GetRequiredService();
return loader.UpdatePluginStatus(_services, id, false);
}
+
+ private async Task IsValidUser()
+ {
+ var userService = _services.GetRequiredService();
+ return await userService.IsAdminUser(_user.Id);
+ }
}
diff --git a/src/Infrastructure/BotSharp.OpenAPI/Controllers/RoleController.cs b/src/Infrastructure/BotSharp.OpenAPI/Controllers/RoleController.cs
new file mode 100644
index 00000000..d4ca64fc
--- /dev/null
+++ b/src/Infrastructure/BotSharp.OpenAPI/Controllers/RoleController.cs
@@ -0,0 +1,88 @@
+using BotSharp.Abstraction.Roles;
+using BotSharp.Abstraction.Users.Enums;
+
+namespace BotSharp.OpenAPI.Controllers;
+
+[Authorize]
+[ApiController]
+public class RoleController : ControllerBase
+{
+ private readonly IServiceProvider _services;
+ private readonly IRoleService _roleService;
+ private readonly IUserIdentity _user;
+
+ public RoleController(
+ IServiceProvider services,
+ IRoleService roleService,
+ IUserIdentity user)
+ {
+ _services = services;
+ _roleService = roleService;
+ _user = user;
+ }
+
+ [HttpPost("/role/refresh")]
+ public async Task RefreshRoles()
+ {
+ var isValid = await IsValidUser();
+ if (!isValid)
+ {
+ return false;
+ }
+
+ return await _roleService.RefreshRoles();
+ }
+
+
+ [HttpGet("/role/options")]
+ public async Task> GetRoleOptions()
+ {
+ return await _roleService.GetRoleOptions();
+ }
+
+ [HttpPost("/roles")]
+ public async Task> GetRoles([FromBody] RoleFilter? filter = null)
+ {
+ if (filter == null)
+ {
+ filter = RoleFilter.Empty();
+ }
+
+ var isValid = await IsValidUser();
+ if (!isValid)
+ {
+ return Enumerable.Empty();
+ }
+
+ var roles = await _roleService.GetRoles(filter);
+ return roles.Select(x => RoleViewModel.FromRole(x)).ToList();
+ }
+
+ [HttpGet("/role/{id}/details")]
+ public async Task GetRoleDetails([FromRoute] string id)
+ {
+ var role = await _roleService.GetRoleDetails(id, includeAgent: true);
+ return RoleViewModel.FromRole(role);
+ }
+
+ [HttpPut("/role")]
+ public async Task UpdateRole([FromBody] RoleUpdateModel model)
+ {
+ if (model == null) return false;
+
+ var isValid = await IsValidUser();
+ if (!isValid)
+ {
+ return false;
+ }
+
+ var role = RoleUpdateModel.ToRole(model);
+ return await _roleService.UpdateRole(role, isUpdateRoleAgents: true);
+ }
+
+ private async Task IsValidUser()
+ {
+ var userService = _services.GetRequiredService();
+ return await userService.IsAdminUser(_user.Id);
+ }
+}
diff --git a/src/Infrastructure/BotSharp.OpenAPI/Controllers/UserController.cs b/src/Infrastructure/BotSharp.OpenAPI/Controllers/UserController.cs
index 0b647e0d..9d652a3e 100644
--- a/src/Infrastructure/BotSharp.OpenAPI/Controllers/UserController.cs
+++ b/src/Infrastructure/BotSharp.OpenAPI/Controllers/UserController.cs
@@ -182,8 +182,8 @@ public class UserController : ControllerBase
public async Task> GetUsers([FromBody] UserFilter filter)
{
var userService = _services.GetRequiredService();
- var user = await userService.GetUser(_user.Id);
- if (user == null || !UserConstant.AdminRoles.Contains(user.Role))
+ var isValid = await IsValidUser();
+ if (!isValid)
{
return new PagedItems();
}
@@ -198,19 +198,26 @@ public class UserController : ControllerBase
};
}
+ [HttpGet("/user/{id}/details")]
+ public async Task GetUserDetails(string id)
+ {
+ var userService = _services.GetRequiredService();
+ var user = await userService.GetUserDetails(id, includeAgent: true);
+ return UserViewModel.FromUser(user);
+ }
[HttpPut("/user")]
public async Task UpdateUser([FromBody] UserUpdateModel model)
{
if (model == null) return false;
- var userService = _services.GetRequiredService();
- var user = await userService.GetUser(_user.Id);
- if (user == null || !UserConstant.AdminRoles.Contains(user.Role))
+ var isValid = await IsValidUser();
+ if (!isValid)
{
return false;
}
+ var userService = _services.GetRequiredService();
var updated = await userService.UpdateUser(UserUpdateModel.ToUser(model), isUpdateUserAgents: true);
return updated;
}
@@ -245,6 +252,12 @@ public class UserController : ControllerBase
#region Private methods
+ private async Task IsValidUser()
+ {
+ var userService = _services.GetRequiredService();
+ return await userService.IsAdminUser(_user.Id);
+ }
+
private FileContentResult BuildFileResult(string file)
{
var fileStorage = _services.GetRequiredService();
diff --git a/src/Infrastructure/BotSharp.OpenAPI/Using.cs b/src/Infrastructure/BotSharp.OpenAPI/Using.cs
index 3542cef9..602f4437 100644
--- a/src/Infrastructure/BotSharp.OpenAPI/Using.cs
+++ b/src/Infrastructure/BotSharp.OpenAPI/Using.cs
@@ -32,3 +32,4 @@ global using BotSharp.OpenAPI.ViewModels.Conversations;
global using BotSharp.OpenAPI.ViewModels.Users;
global using BotSharp.OpenAPI.ViewModels.Agents;
global using BotSharp.OpenAPI.ViewModels.Files;
+global using BotSharp.OpenAPI.ViewModels.Roles;
\ No newline at end of file
diff --git a/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Agents/AgentViewModel.cs b/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Agents/AgentViewModel.cs
index b368cde0..ca814177 100644
--- a/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Agents/AgentViewModel.cs
+++ b/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Agents/AgentViewModel.cs
@@ -48,6 +48,8 @@ public class AgentViewModel
public bool Editable { get; set; }
public bool Chatable { get; set; }
+ public bool Trainable { get; set; }
+ public bool Evaluable { get; set; }
[JsonPropertyName("created_datetime")]
public DateTime CreatedDateTime { get; set; }
diff --git a/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Knowledges/VectorKnowledgeUploadRequest.cs b/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Knowledges/VectorKnowledgeUploadRequest.cs
index 3788f05c..0934c508 100644
--- a/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Knowledges/VectorKnowledgeUploadRequest.cs
+++ b/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Knowledges/VectorKnowledgeUploadRequest.cs
@@ -6,4 +6,7 @@ public class VectorKnowledgeUploadRequest
{
[JsonPropertyName("files")]
public IEnumerable Files { get; set; } = new List();
+
+ [JsonPropertyName("chunk_option")]
+ public ChunkOption? ChunkOption { get; set; }
}
diff --git a/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Roles/RoleAgentActionViewModel.cs b/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Roles/RoleAgentActionViewModel.cs
new file mode 100644
index 00000000..ffd7987e
--- /dev/null
+++ b/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Roles/RoleAgentActionViewModel.cs
@@ -0,0 +1,42 @@
+using BotSharp.Abstraction.Agents.Models;
+using BotSharp.Abstraction.Roles.Models;
+using System.Text.Json.Serialization;
+
+namespace BotSharp.OpenAPI.ViewModels.Roles;
+
+public class RoleAgentActionViewModel
+{
+ [JsonPropertyName("id")]
+ public string? Id { get; set; }
+
+ [JsonPropertyName("agent_id")]
+ public string AgentId { get; set; }
+
+ [JsonPropertyName("agent")]
+ public Agent? Agent { get; set; }
+
+ [JsonPropertyName("actions")]
+ public IEnumerable Actions { get; set; } = [];
+
+
+ public static RoleAgentActionViewModel ToViewModel(RoleAgentAction action)
+ {
+ return new RoleAgentActionViewModel
+ {
+ Id = action.Id,
+ AgentId = action.AgentId,
+ Agent = action.Agent,
+ Actions = action.Actions
+ };
+ }
+
+ public static RoleAgentAction ToDomainModel(RoleAgentActionViewModel action)
+ {
+ return new RoleAgentAction
+ {
+ Id = action.Id,
+ AgentId = action.AgentId,
+ Actions = action.Actions
+ };
+ }
+}
diff --git a/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Roles/RoleUpdateModel.cs b/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Roles/RoleUpdateModel.cs
new file mode 100644
index 00000000..eece96fc
--- /dev/null
+++ b/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Roles/RoleUpdateModel.cs
@@ -0,0 +1,30 @@
+using BotSharp.Abstraction.Roles.Models;
+using System.Text.Json.Serialization;
+
+namespace BotSharp.OpenAPI.ViewModels.Roles;
+
+public class RoleUpdateModel
+{
+ [JsonPropertyName("id")]
+ public string? Id { get; set; }
+
+ [JsonPropertyName("name")]
+ public string Name { get; set; } = null!;
+
+ [JsonPropertyName("permissions")]
+ public IEnumerable Permissions { get; set; } = [];
+
+ [JsonPropertyName("agent_actions")]
+ public IEnumerable AgentActions { get; set; } = [];
+
+ public static Role ToRole(RoleUpdateModel model)
+ {
+ return new Role
+ {
+ Id = model.Id,
+ Name = model.Name,
+ Permissions = model.Permissions,
+ AgentActions = model.AgentActions?.Select(x => RoleAgentActionViewModel.ToDomainModel(x)) ?? []
+ };
+ }
+}
diff --git a/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Roles/RoleViewModel.cs b/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Roles/RoleViewModel.cs
new file mode 100644
index 00000000..d4e8de33
--- /dev/null
+++ b/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Roles/RoleViewModel.cs
@@ -0,0 +1,40 @@
+using BotSharp.Abstraction.Roles.Models;
+using System.Text.Json.Serialization;
+
+namespace BotSharp.OpenAPI.ViewModels.Roles;
+
+public class RoleViewModel
+{
+ [JsonPropertyName("id")]
+ public string? Id { get; set; }
+
+ [JsonPropertyName("name")]
+ public string Name { get; set; } = null!;
+
+ [JsonPropertyName("permissions")]
+ public IEnumerable Permissions { get; set; } = [];
+
+ [JsonPropertyName("agent_actions")]
+ public IEnumerable AgentActions { get; set; } = [];
+
+ [JsonPropertyName("create_date")]
+ public DateTime? CreateDate { get; set; }
+
+ [JsonPropertyName("update_date")]
+ public DateTime? UpdateDate { get; set; }
+
+ public static RoleViewModel FromRole(Role? role)
+ {
+ if (role == null) return null;
+
+ return new RoleViewModel
+ {
+ Id = role.Id,
+ Name = role.Name,
+ Permissions = role.Permissions,
+ AgentActions = role.AgentActions?.Select(x => RoleAgentActionViewModel.ToViewModel(x)) ?? [],
+ CreateDate = role.CreatedTime != default ? role.CreatedTime : null,
+ UpdateDate = role.UpdatedTime != default ? role.UpdatedTime : null
+ };
+ }
+}
diff --git a/src/Plugins/BotSharp.Plugin.KnowledgeBase/Services/KnowledgeService.Document.cs b/src/Plugins/BotSharp.Plugin.KnowledgeBase/Services/KnowledgeService.Document.cs
index dd7e0d1a..8c49f48c 100644
--- a/src/Plugins/BotSharp.Plugin.KnowledgeBase/Services/KnowledgeService.Document.cs
+++ b/src/Plugins/BotSharp.Plugin.KnowledgeBase/Services/KnowledgeService.Document.cs
@@ -10,7 +10,8 @@ namespace BotSharp.Plugin.KnowledgeBase.Services;
public partial class KnowledgeService
{
- public async Task UploadDocumentsToKnowledge(string collectionName, IEnumerable files)
+ public async Task UploadDocumentsToKnowledge(string collectionName,
+ IEnumerable files, ChunkOption? option = null)
{
var res = new UploadKnowledgeResponse
{
@@ -48,7 +49,7 @@ public partial class KnowledgeService
{
// Get document info
var (contentType, bytes) = await GetFileInfo(file);
- var contents = await GetFileContent(contentType, bytes);
+ var contents = await GetFileContent(contentType, bytes, option ?? ChunkOption.Default());
// Save document
var fileId = Guid.NewGuid();
@@ -369,13 +370,13 @@ public partial class KnowledgeService
}
#region Read doc content
- private async Task> GetFileContent(string contentType, byte[] bytes)
+ private async Task> GetFileContent(string contentType, byte[] bytes, ChunkOption option)
{
IEnumerable results = new List();
if (contentType.IsEqualTo(MediaTypeNames.Text.Plain))
{
- results = await ReadTxt(bytes);
+ results = await ReadTxt(bytes, option);
}
else if (contentType.IsEqualTo(MediaTypeNames.Application.Pdf))
{
@@ -385,7 +386,7 @@ public partial class KnowledgeService
return results;
}
- private async Task> ReadTxt(byte[] bytes)
+ private async Task> ReadTxt(byte[] bytes, ChunkOption option)
{
using var stream = new MemoryStream(bytes);
using var reader = new StreamReader(stream);
@@ -393,12 +394,7 @@ public partial class KnowledgeService
reader.Close();
stream.Close();
- var lines = TextChopper.Chop(content, new ChunkOption
- {
- Size = 1024,
- Conjunction = 12,
- SplitByWord = true,
- });
+ var lines = TextChopper.Chop(content, option);
return lines;
}
diff --git a/src/Plugins/BotSharp.Plugin.LangChain/BotSharp.Plugin.VertexAI.csproj b/src/Plugins/BotSharp.Plugin.LangChain/BotSharp.Plugin.VertexAI.csproj
new file mode 100644
index 00000000..004d7233
--- /dev/null
+++ b/src/Plugins/BotSharp.Plugin.LangChain/BotSharp.Plugin.VertexAI.csproj
@@ -0,0 +1,21 @@
+
+
+
+ $(TargetFramework)
+ enable
+ $(LangVersion)
+ $(BotSharpVersion)
+ $(GeneratePackageOnBuild)
+ $(GenerateDocumentationFile)
+ $(SolutionDir)packages
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Plugins/BotSharp.Plugin.LangChain/Providers/ChatCompletionProvider.cs b/src/Plugins/BotSharp.Plugin.LangChain/Providers/ChatCompletionProvider.cs
new file mode 100644
index 00000000..61f66119
--- /dev/null
+++ b/src/Plugins/BotSharp.Plugin.LangChain/Providers/ChatCompletionProvider.cs
@@ -0,0 +1,71 @@
+using BotSharp.Abstraction.Agents.Enums;
+using BotSharp.Abstraction.Agents.Models;
+using BotSharp.Abstraction.Conversations.Models;
+using BotSharp.Abstraction.Loggers;
+using BotSharp.Abstraction.MLTasks;
+using LangChain.Providers;
+using LangChain.Providers.Google.VertexAI;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Logging;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+
+namespace BotSharp.Plugin.VertexAI.Providers
+{
+ public class ChatCompletionProvider(VertexAIConfiguration config,
+ ChatSettings settings,
+ ILogger logger,
+ IServiceProvider services) : IChatCompletion
+ {
+ public string Provider => "vertexai";
+ private readonly VertexAIConfiguration _config = config;
+ private readonly ChatSettings? _settings = settings;
+ private readonly IServiceProvider _services = services;
+ private readonly ILogger _logger = logger;
+ public required string _model;
+
+ public void SetModelName(string model)
+ {
+ _model = model;
+ }
+
+ public async Task GetChatCompletions(Agent agent, List conversations)
+ {
+ var hooks = _services.GetServices().ToList();
+ Task.WaitAll(hooks.Select(hook =>
+ hook.BeforeGenerating(agent, conversations)).ToArray());
+ var client = new VertexAIProvider(_config);
+ var model = new VertexAIChatModel(client, _model);
+ var messages = conversations
+ .Select(c => new Message(c.Content, c.Role == AgentRole.User ? MessageRole.Human : MessageRole.Ai)).ToList();
+
+ var response = await model.GenerateAsync(new ChatRequest { Messages = messages }, _settings);
+
+ var msg = new RoleDialogModel(MessageRole.Ai.ToString(), response.LastMessageContent)
+ {
+ CurrentAgentId = agent.Id
+ };
+
+ Task.WaitAll(hooks.Select(hook =>
+ hook.AfterGenerated(msg, new TokenStatsModel
+ {
+ Prompt = response.Messages[0].Content,
+ Model = _model
+ })).ToArray());
+
+ return msg;
+ }
+
+ public Task GetChatCompletionsAsync(Agent agent, List conversations, Func onMessageReceived, Func onFunctionExecuting)
+ {
+ throw new NotImplementedException();
+ }
+
+ public Task GetChatCompletionsStreamingAsync(Agent agent, List conversations, Func onMessageReceived)
+ {
+ throw new NotImplementedException();
+ }
+ }
+}
diff --git a/src/Plugins/BotSharp.Plugin.LangChain/Providers/TextCompletionProvider.cs b/src/Plugins/BotSharp.Plugin.LangChain/Providers/TextCompletionProvider.cs
new file mode 100644
index 00000000..46186452
--- /dev/null
+++ b/src/Plugins/BotSharp.Plugin.LangChain/Providers/TextCompletionProvider.cs
@@ -0,0 +1,64 @@
+using BotSharp.Abstraction.Agents.Enums;
+using BotSharp.Abstraction.Agents.Models;
+using BotSharp.Abstraction.Conversations.Models;
+using BotSharp.Abstraction.Loggers;
+using BotSharp.Abstraction.MLTasks;
+using LangChain.Providers;
+using LangChain.Providers.Google.VertexAI;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Logging;
+using System;
+using System.Linq;
+using System.Threading.Tasks;
+
+namespace BotSharp.Plugin.VertexAI.Providers
+{
+ public class TextCompletionProvider(VertexAIConfiguration config,
+ ChatSettings settings,
+ ILogger logger,
+ IServiceProvider services) : ITextCompletion
+ {
+ public string Provider => "vertexai";
+ private readonly VertexAIConfiguration _config = config;
+ private readonly ChatSettings? _settings = settings;
+ private readonly IServiceProvider _services = services;
+ private readonly ILogger _logger = logger;
+ public required string _model;
+
+ public async Task GetCompletion(string text, string agentId, string messageId)
+ {
+ var contentHooks = _services.GetServices().ToList();
+ var agent = new Agent()
+ {
+ Id = agentId,
+ };
+
+ var client = new VertexAIProvider(_config);
+ var model = new VertexAIChatModel(client, _model);
+ var response = await model.GenerateAsync(text, _settings);
+
+ var responseMessage = new RoleDialogModel(AgentRole.Assistant, response.LastMessageContent)
+ {
+ CurrentAgentId = agentId,
+ MessageId = messageId
+ };
+
+ Task.WaitAll(contentHooks.Select(hook =>
+ hook.AfterGenerated(responseMessage, new TokenStatsModel
+ {
+ Prompt = text,
+ Provider = Provider,
+ Model = _model,
+ PromptCount = response.Usage.TotalTokens,
+ CompletionCount = response.Usage.OutputTokens
+ })).ToArray());
+
+ return response.LastMessageContent;
+ }
+
+ public void SetModelName(string model)
+ {
+ _model = model;
+ }
+ }
+}
diff --git a/src/Plugins/BotSharp.Plugin.LangChain/VertexAiPlugin.cs b/src/Plugins/BotSharp.Plugin.LangChain/VertexAiPlugin.cs
new file mode 100644
index 00000000..5ea7612d
--- /dev/null
+++ b/src/Plugins/BotSharp.Plugin.LangChain/VertexAiPlugin.cs
@@ -0,0 +1,28 @@
+using BotSharp.Abstraction.MLTasks;
+using BotSharp.Abstraction.Plugins;
+using BotSharp.Abstraction.Settings;
+using BotSharp.Plugin.VertexAI.Providers;
+using LangChain.Providers.Google.VertexAI;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.DependencyInjection;
+
+namespace BotSharp.Plugin.VertexAI;
+
+public class VertexAiPlugin : IBotSharpPlugin
+{
+ public string Id => "962ff441-2b40-4db4-b530-49efb1688a75";
+ public string Name => "VertexAI";
+ public string Description => "VertexAI Service including text generation, text to image and other AI services.";
+ public string IconUrl => "https://upload.wikimedia.org/wikipedia/commons/thumb/0/05/Vertex_AI_Logo.svg/480px-Vertex_AI_Logo.svg.png";
+
+ public void RegisterDI(IServiceCollection services, IConfiguration config)
+ {
+ services.AddScoped(provider =>
+ {
+ var settingService = provider.GetRequiredService();
+ return settingService.Bind("VertexAI");
+ });
+ services.AddScoped();
+ services.AddScoped();
+ }
+}
diff --git a/src/Plugins/BotSharp.Plugin.MongoStorage/Collections/RoleAgentDocument.cs b/src/Plugins/BotSharp.Plugin.MongoStorage/Collections/RoleAgentDocument.cs
new file mode 100644
index 00000000..037158d0
--- /dev/null
+++ b/src/Plugins/BotSharp.Plugin.MongoStorage/Collections/RoleAgentDocument.cs
@@ -0,0 +1,25 @@
+using BotSharp.Abstraction.Roles.Models;
+
+namespace BotSharp.Plugin.MongoStorage.Collections;
+
+public class RoleAgentDocument : MongoBase
+{
+ public string RoleId { get; set; }
+ public string AgentId { get; set; }
+ public IEnumerable Actions { get; set; } = [];
+ public DateTime CreatedTime { get; set; }
+ public DateTime UpdatedTime { get; set; }
+
+ public RoleAgent ToRoleAgent()
+ {
+ return new RoleAgent
+ {
+ Id = Id,
+ RoleId = RoleId,
+ AgentId = AgentId,
+ Actions = Actions,
+ CreatedTime = CreatedTime,
+ UpdatedTime = UpdatedTime
+ };
+ }
+}
diff --git a/src/Plugins/BotSharp.Plugin.MongoStorage/Collections/RoleDocument.cs b/src/Plugins/BotSharp.Plugin.MongoStorage/Collections/RoleDocument.cs
new file mode 100644
index 00000000..557f219a
--- /dev/null
+++ b/src/Plugins/BotSharp.Plugin.MongoStorage/Collections/RoleDocument.cs
@@ -0,0 +1,24 @@
+using BotSharp.Abstraction.Roles.Models;
+
+namespace BotSharp.Plugin.MongoStorage.Collections;
+
+public class RoleDocument : MongoBase
+{
+ public string Name { get; set; }
+ public IEnumerable Permissions { get; set; } = [];
+ public DateTime CreatedTime { get; set; }
+ public DateTime UpdatedTime { get; set; }
+
+
+ public Role ToRole()
+ {
+ return new Role
+ {
+ Id = Id,
+ Name = Name,
+ Permissions = Permissions,
+ CreatedTime = CreatedTime,
+ UpdatedTime = UpdatedTime
+ };
+ }
+}
diff --git a/src/Plugins/BotSharp.Plugin.MongoStorage/MongoDbContext.cs b/src/Plugins/BotSharp.Plugin.MongoStorage/MongoDbContext.cs
index af7c8b8f..b91c35fe 100644
--- a/src/Plugins/BotSharp.Plugin.MongoStorage/MongoDbContext.cs
+++ b/src/Plugins/BotSharp.Plugin.MongoStorage/MongoDbContext.cs
@@ -159,4 +159,10 @@ public class MongoDbContext
public IMongoCollection KnowledgeCollectionFileMeta
=> Database.GetCollection($"{_collectionPrefix}_KnowledgeCollectionFileMeta");
+
+ public IMongoCollection Roles
+ => Database.GetCollection($"{_collectionPrefix}_Roles");
+
+ public IMongoCollection RoleAgents
+ => Database.GetCollection($"{_collectionPrefix}_RoleAgents");
}
diff --git a/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.Agent.cs b/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.Agent.cs
index 44a4e2bc..d17f7f2f 100644
--- a/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.Agent.cs
+++ b/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.Agent.cs
@@ -2,6 +2,7 @@ using BotSharp.Abstraction.Agents.Models;
using BotSharp.Abstraction.Functions.Models;
using BotSharp.Abstraction.Repositories.Filters;
using BotSharp.Abstraction.Routing.Models;
+using MongoDB.Driver;
namespace BotSharp.Plugin.MongoStorage.Repository;
@@ -283,6 +284,11 @@ public partial class MongoRepository
public List GetAgents(AgentFilter filter)
{
+ if (filter == null)
+ {
+ filter = AgentFilter.Empty();
+ }
+
var agents = new List();
var builder = Builders.Filter;
var filters = new List>() { builder.Empty };
@@ -292,6 +298,11 @@ public partial class MongoRepository
filters.Add(builder.Eq(x => x.Name, filter.AgentName));
}
+ if (!string.IsNullOrEmpty(filter.SimilarName))
+ {
+ filters.Add(builder.Regex(x => x.Name, new BsonRegularExpression(filter.SimilarName, "i")));
+ }
+
if (filter.Disabled.HasValue)
{
filters.Add(builder.Eq(x => x.Disabled, filter.Disabled.Value));
@@ -327,8 +338,6 @@ public partial class MongoRepository
if (found.IsNullOrEmpty()) return [];
- var agentIds = found.Select(x => x.AgentId).Distinct().ToList();
- var agents = GetAgents(new AgentFilter { AgentIds = agentIds });
var res = found.Select(x => new UserAgent
{
Id = x.Id,
@@ -339,6 +348,8 @@ public partial class MongoRepository
UpdatedTime = x.UpdatedTime
}).ToList();
+ var agentIds = found.Select(x => x.AgentId).Distinct().ToList();
+ var agents = GetAgents(new AgentFilter { AgentIds = agentIds });
foreach (var item in res)
{
var agent = agents.FirstOrDefault(x => x.Id == item.AgentId);
@@ -450,6 +461,7 @@ public partial class MongoRepository
try
{
_dc.UserAgents.DeleteMany(Builders.Filter.Empty);
+ _dc.RoleAgents.DeleteMany(Builders.Filter.Empty);
_dc.Agents.DeleteMany(Builders.Filter.Empty);
return true;
}
@@ -467,10 +479,12 @@ public partial class MongoRepository
var agentFilter = Builders.Filter.Eq(x => x.Id, agentId);
var userAgentFilter = Builders.Filter.Eq(x => x.AgentId, agentId);
+ var roleAgentFilter = Builders.Filter.Eq(x => x.AgentId, agentId);
var agentTaskFilter = Builders.Filter.Eq(x => x.AgentId, agentId);
_dc.Agents.DeleteOne(agentFilter);
_dc.UserAgents.DeleteMany(userAgentFilter);
+ _dc.RoleAgents.DeleteMany(roleAgentFilter);
_dc.AgentTasks.DeleteMany(agentTaskFilter);
return true;
}
diff --git a/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.AgentTask.cs b/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.AgentTask.cs
index a00858c4..2946b7b0 100644
--- a/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.AgentTask.cs
+++ b/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.AgentTask.cs
@@ -8,6 +8,11 @@ public partial class MongoRepository
#region Task
public PagedItems GetAgentTasks(AgentTaskFilter filter)
{
+ if (filter == null)
+ {
+ filter = AgentTaskFilter.Empty();
+ }
+
var pager = filter.Pager ?? new Pagination();
var builder = Builders.Filter;
var filters = new List>() { builder.Empty };
diff --git a/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.Conversation.cs b/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.Conversation.cs
index 85fa1033..ccbfc1af 100644
--- a/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.Conversation.cs
+++ b/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.Conversation.cs
@@ -281,6 +281,11 @@ public partial class MongoRepository
public PagedItems GetConversations(ConversationFilter filter)
{
+ if (filter == null)
+ {
+ filter = ConversationFilter.Empty();
+ }
+
var convBuilder = Builders.Filter;
var convFilters = new List>() { convBuilder.Empty };
diff --git a/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.Role.cs b/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.Role.cs
new file mode 100644
index 00000000..958edf0f
--- /dev/null
+++ b/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.Role.cs
@@ -0,0 +1,157 @@
+using BotSharp.Abstraction.Repositories.Filters;
+using BotSharp.Abstraction.Roles.Models;
+
+namespace BotSharp.Plugin.MongoStorage.Repository;
+
+public partial class MongoRepository
+{
+ public bool RefreshRoles(IEnumerable roles)
+ {
+ if (roles.IsNullOrEmpty()) return false;
+
+ var validRoles = roles.Where(x => !string.IsNullOrWhiteSpace(x.Id)
+ && !string.IsNullOrWhiteSpace(x.Name)).ToList();
+ if (validRoles.IsNullOrEmpty()) return false;
+
+
+ // Clear data
+ _dc.RoleAgents.DeleteMany(Builders.Filter.Empty);
+ _dc.Roles.DeleteMany(Builders.Filter.Empty);
+
+ var roleDocs = validRoles.Select(x => new RoleDocument
+ {
+ Id = x.Id,
+ Name = x.Name,
+ Permissions = x.Permissions,
+ CreatedTime = DateTime.UtcNow,
+ UpdatedTime = DateTime.UtcNow
+ });
+ _dc.Roles.InsertMany(roleDocs);
+
+ return true;
+ }
+
+
+ public IEnumerable GetRoles(RoleFilter filter)
+ {
+ if (filter == null)
+ {
+ filter = RoleFilter.Empty();
+ }
+
+ var roleBuilder = Builders.Filter;
+ var roleFilters = new List>() { roleBuilder.Empty };
+
+ // Apply filters
+ if (!filter.Names.IsNullOrEmpty())
+ {
+ roleFilters.Add(roleBuilder.In(x => x.Name, filter.Names));
+ }
+
+ if (!filter.ExcludeRoles.IsNullOrEmpty())
+ {
+ roleFilters.Add(roleBuilder.Nin(x => x.Name, filter.ExcludeRoles));
+ }
+
+ // Search
+ var roleDocs = _dc.Roles.Find(roleBuilder.And(roleFilters)).ToList();
+ var roles = roleDocs.Select(x => x.ToRole()).ToList();
+
+ return roles;
+ }
+
+ public Role? GetRoleDetails(string roleId, bool includeAgent = false)
+ {
+ if (string.IsNullOrWhiteSpace(roleId)) return null;
+
+ var roleDoc = _dc.Roles.Find(Builders.Filter.Eq(x => x.Id, roleId)).FirstOrDefault();
+ if (roleDoc == null) return null;
+
+ var agentActions = new List();
+ var role = roleDoc.ToRole();
+ var roleAgentDocs = _dc.RoleAgents.Find(Builders.Filter.Eq(x => x.RoleId, roleId)).ToList();
+
+ if (!includeAgent)
+ {
+ agentActions = roleAgentDocs.Select(x => new RoleAgentAction
+ {
+ Id = x.Id,
+ AgentId = x.AgentId,
+ Actions = x.Actions
+ }).ToList();
+ role.AgentActions = agentActions;
+ return role;
+ }
+
+ var agentIds = roleAgentDocs.Select(x => x.AgentId).Distinct().ToList();
+ if (!agentIds.IsNullOrEmpty())
+ {
+ var agents = GetAgents(new AgentFilter { AgentIds = agentIds });
+
+ foreach (var item in roleAgentDocs)
+ {
+ var found = agents.FirstOrDefault(x => x.Id == item.AgentId);
+ if (found == null) continue;
+
+ agentActions.Add(new RoleAgentAction
+ {
+ Id = item.Id,
+ AgentId = found.Id,
+ Agent = found,
+ Actions = item.Actions
+ });
+ }
+ }
+
+ role.AgentActions = agentActions;
+ return role;
+ }
+
+ public bool UpdateRole(Role role, bool updateRoleAgents = false)
+ {
+ if (string.IsNullOrEmpty(role?.Id)) return false;
+
+ var roleFilter = Builders.Filter.Eq(x => x.Id, role.Id);
+ var roleUpdate = Builders.Update
+ .Set(x => x.Name, role.Name)
+ .Set(x => x.Permissions, role.Permissions)
+ .Set(x => x.CreatedTime, DateTime.UtcNow)
+ .Set(x => x.UpdatedTime, DateTime.UtcNow);
+
+ _dc.Roles.UpdateOne(roleFilter, roleUpdate, _options);
+
+ if (updateRoleAgents)
+ {
+ var roleAgentDocs = role.AgentActions?.Select(x => new RoleAgentDocument
+ {
+ Id = !string.IsNullOrEmpty(x.Id) ? x.Id : Guid.NewGuid().ToString(),
+ RoleId = role.Id,
+ AgentId = x.AgentId,
+ Actions = x.Actions,
+ CreatedTime = DateTime.UtcNow,
+ UpdatedTime = DateTime.UtcNow
+ })?.ToList() ?? [];
+
+ var toDelete = _dc.RoleAgents.Find(Builders.Filter.And(
+ Builders.Filter.Eq(x => x.RoleId, role.Id),
+ Builders.Filter.Nin(x => x.Id, roleAgentDocs.Select(x => x.Id))
+ )).ToList();
+
+ _dc.RoleAgents.DeleteMany(Builders.Filter.In(x => x.Id, toDelete.Select(x => x.Id)));
+ foreach (var doc in roleAgentDocs)
+ {
+ var roleAgentFilter = Builders.Filter.Eq(x => x.Id, doc.Id);
+ var roleAgentUpdate = Builders.Update
+ .Set(x => x.Id, doc.Id)
+ .Set(x => x.RoleId, role.Id)
+ .Set(x => x.AgentId, doc.AgentId)
+ .Set(x => x.Actions, doc.Actions)
+ .Set(x => x.UpdatedTime, DateTime.UtcNow);
+
+ _dc.RoleAgents.UpdateOne(roleAgentFilter, roleAgentUpdate, _options);
+ }
+ }
+
+ return true;
+ }
+}
diff --git a/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.User.cs b/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.User.cs
index 01ed871e..071e58f3 100644
--- a/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.User.cs
+++ b/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.User.cs
@@ -2,6 +2,8 @@ using BotSharp.Abstraction.Agents.Models;
using BotSharp.Abstraction.Repositories.Filters;
using BotSharp.Abstraction.Users.Enums;
using BotSharp.Abstraction.Users.Models;
+using MongoDB.Driver;
+using System.Globalization;
namespace BotSharp.Plugin.MongoStorage.Repository;
@@ -15,7 +17,9 @@ public partial class MongoRepository
public User? GetUserByPhone(string phone, string role = null, string regionCode = "CN")
{
- if (string.IsNullOrWhiteSpace(phone))
+ string phoneSecond = string.Empty;
+ // if phone number length is less than 4, return null
+ if (string.IsNullOrWhiteSpace(phone) || phone?.Length < 4)
{
return null;
}
@@ -36,22 +40,19 @@ public partial class MongoRepository
public User? GetUserById(string id)
{
- var user = _dc.Users.AsQueryable()
- .FirstOrDefault(x => x.Id == id || (x.ExternalId != null && x.ExternalId == id));
+ var user = _dc.Users.AsQueryable().FirstOrDefault(x => x.Id == id || (x.ExternalId != null && x.ExternalId == id));
return user != null ? user.ToUser() : null;
}
public List GetUserByIds(List ids)
{
- var users = _dc.Users.AsQueryable()
- .Where(x => ids.Contains(x.Id) || (x.ExternalId != null && ids.Contains(x.ExternalId))).ToList();
+ var users = _dc.Users.AsQueryable().Where(x => ids.Contains(x.Id) || (x.ExternalId != null && ids.Contains(x.ExternalId))).ToList();
return users?.Any() == true ? users.Select(x => x.ToUser()).ToList() : new List();
}
public List GetUsersByAffiliateId(string affiliateId)
{
- var users = _dc.Users.AsQueryable()
- .Where(x => x.AffiliateId == affiliateId).ToList();
+ var users = _dc.Users.AsQueryable().Where(x => x.AffiliateId == affiliateId).ToList();
return users?.Any() == true ? users.Select(x => x.ToUser()).ToList() : new List();
}
@@ -168,6 +169,11 @@ public partial class MongoRepository
public PagedItems GetUsers(UserFilter filter)
{
+ if (filter == null)
+ {
+ filter = UserFilter.Empty();
+ }
+
var userBuilder = Builders.Filter;
var userFilters = new List>() { userBuilder.Empty };
@@ -188,6 +194,10 @@ public partial class MongoRepository
{
userFilters.Add(userBuilder.In(x => x.Role, filter.Roles));
}
+ if (!filter.Types.IsNullOrEmpty())
+ {
+ userFilters.Add(userBuilder.In(x => x.Type, filter.Types));
+ }
if (!filter.Sources.IsNullOrEmpty())
{
userFilters.Add(userBuilder.In(x => x.Source, filter.Sources));
@@ -202,44 +212,6 @@ public partial class MongoRepository
var count = _dc.Users.CountDocuments(filterDef);
var users = userDocs.Select(x => x.ToUser()).ToList();
- var userIds = users.Select(x => x.Id).ToList();
- var userAgents = _dc.UserAgents.AsQueryable().Where(x => userIds.Contains(x.UserId)).Select(x => new UserAgent
- {
- Id = x.Id,
- UserId = x.UserId,
- AgentId = x.AgentId,
- Actions = x.Actions ?? Enumerable.Empty(),
- CreatedTime = x.CreatedTime,
- UpdatedTime = x.UpdatedTime
- }).ToList();
- var agentIds = userAgents.Select(x => x.AgentId).Distinct().ToList();
-
- if (!agentIds.IsNullOrEmpty())
- {
- var agents = GetAgents(new AgentFilter { AgentIds = agentIds });
- foreach (var item in userAgents)
- {
- var agent = agents.FirstOrDefault(x => x.Id == item.AgentId);
- if (agent == null) continue;
-
- item.Agent = agent;
- }
-
- foreach (var user in users)
- {
- var found = userAgents.Where(x => x.UserId == user.Id).ToList();
- if (found.IsNullOrEmpty()) continue;
-
- user.AgentActions = found.Select(x => new UserAgentAction
- {
- Id = x.Id,
- AgentId = x.AgentId,
- Agent = x.Agent,
- Actions = x.Actions
- });
- }
- }
-
return new PagedItems
{
Items = users,
@@ -247,8 +219,60 @@ public partial class MongoRepository
};
}
+ public User? GetUserDetails(string userId, bool includeAgent = false)
+ {
+ if (string.IsNullOrWhiteSpace(userId)) return null;
- public bool UpdateUser(User user, bool isUpdateUserAgents = false)
+ var userDoc = _dc.Users.AsQueryable().FirstOrDefault(x => x.Id == userId || x.ExternalId == userId);
+ if (userDoc == null) return null;
+
+ var agentActions = new List();
+ var user = userDoc.ToUser();
+ var userAgents = _dc.UserAgents.AsQueryable().Where(x => x.UserId == userId).Select(x => new UserAgent
+ {
+ Id = x.Id,
+ UserId = x.UserId,
+ AgentId = x.AgentId,
+ Actions = x.Actions ?? Enumerable.Empty()
+ }).ToList();
+
+ if (!includeAgent)
+ {
+ agentActions = userAgents.Select(x => new UserAgentAction
+ {
+ Id = x.Id,
+ AgentId = x.AgentId,
+ Actions = x.Actions
+ }).ToList();
+ user.AgentActions = agentActions;
+ return user;
+ }
+
+ var agentIds = userAgents.Select(x => x.AgentId)?.Distinct().ToList();
+ if (!agentIds.IsNullOrEmpty())
+ {
+ var agents = GetAgents(new AgentFilter { AgentIds = agentIds });
+
+ foreach (var item in userAgents)
+ {
+ var found = agents.FirstOrDefault(x => x.Id == item.AgentId);
+ if (found == null) continue;
+
+ agentActions.Add(new UserAgentAction
+ {
+ Id = item.Id,
+ AgentId = found.Id,
+ Agent = found,
+ Actions = item.Actions
+ });
+ }
+ }
+
+ user.AgentActions = agentActions;
+ return user;
+ }
+
+ public bool UpdateUser(User user, bool updateUserAgents = false)
{
if (string.IsNullOrEmpty(user?.Id)) return false;
@@ -261,7 +285,7 @@ public partial class MongoRepository
_dc.Users.UpdateOne(userFilter, userUpdate);
- if (isUpdateUserAgents)
+ if (updateUserAgents)
{
var userAgentDocs = user.AgentActions?.Select(x => new UserAgentDocument
{
diff --git a/src/Plugins/BotSharp.Plugin.Planner/Functions/SecondaryStagePlanFn.cs b/src/Plugins/BotSharp.Plugin.Planner/Functions/SecondaryStagePlanFn.cs
index f0972ccf..be780f51 100644
--- a/src/Plugins/BotSharp.Plugin.Planner/Functions/SecondaryStagePlanFn.cs
+++ b/src/Plugins/BotSharp.Plugin.Planner/Functions/SecondaryStagePlanFn.cs
@@ -95,6 +95,8 @@ public class SecondaryStagePlanFn : IFunctionCallback
var conv = _services.GetRequiredService();
var wholeDialogs = conv.GetDialogHistory();
+ wholeDialogs.Last().Content += "\r\nOutput in JSON format.";
+
var completion = CompletionProvider.GetChatCompletion(_services,
provider: plannerAgent.LlmConfig.Provider,
model: plannerAgent.LlmConfig.Model);
diff --git a/src/Plugins/BotSharp.Plugin.Planner/Functions/SummaryPlanFn.cs b/src/Plugins/BotSharp.Plugin.Planner/Functions/SummaryPlanFn.cs
index 7b5134a0..2f5a1708 100644
--- a/src/Plugins/BotSharp.Plugin.Planner/Functions/SummaryPlanFn.cs
+++ b/src/Plugins/BotSharp.Plugin.Planner/Functions/SummaryPlanFn.cs
@@ -72,8 +72,12 @@ public class SummaryPlanFn : IFunctionCallback
message.Content = summary.Content;
// Validate the sql result
- await fn.InvokeFunction("validate_sql", message);
-
+ var args = JsonSerializer.Deserialize(message.FunctionArgs);
+ if (args.IsSqlTemplate == false)
+ {
+ await fn.InvokeFunction("validate_sql", message);
+ }
+
await HookEmitter.Emit(_services, async hook =>
await hook.OnPlanningCompleted(nameof(TwoStageTaskPlanner), message)
);
diff --git a/src/Plugins/BotSharp.Plugin.Planner/TwoStaging/Models/SummaryPlan.cs b/src/Plugins/BotSharp.Plugin.Planner/TwoStaging/Models/SummaryPlan.cs
new file mode 100644
index 00000000..459237a8
--- /dev/null
+++ b/src/Plugins/BotSharp.Plugin.Planner/TwoStaging/Models/SummaryPlan.cs
@@ -0,0 +1,7 @@
+namespace BotSharp.Plugin.Planner.TwoStaging.Models;
+
+public class SummaryPlan
+{
+ [JsonPropertyName("is_sql_template")]
+ public bool IsSqlTemplate { get; set; } = false;
+}
diff --git a/src/Plugins/BotSharp.Plugin.Planner/data/agents/282a7128-69a1-44b0-878c-a9159b88f3b9/functions/plan_summary.json b/src/Plugins/BotSharp.Plugin.Planner/data/agents/282a7128-69a1-44b0-878c-a9159b88f3b9/functions/plan_summary.json
index 7e459503..c13bfb25 100644
--- a/src/Plugins/BotSharp.Plugin.Planner/data/agents/282a7128-69a1-44b0-878c-a9159b88f3b9/functions/plan_summary.json
+++ b/src/Plugins/BotSharp.Plugin.Planner/data/agents/282a7128-69a1-44b0-878c-a9159b88f3b9/functions/plan_summary.json
@@ -4,6 +4,10 @@
"parameters": {
"type": "object",
"properties": {
+ "is_sql_template": {
+ "type": "boolean",
+ "description": "If user request is to generate sql template instead of actual sql statement."
+ },
"related_tables": {
"type": "array",
"description": "table name in planning steps",
@@ -13,6 +17,6 @@
}
}
},
- "required": [ "related_tables" ]
+ "required": [ "related_tables", "is_sql_template" ]
}
}
\ No newline at end of file
diff --git a/src/Plugins/BotSharp.Plugin.Planner/data/agents/282a7128-69a1-44b0-878c-a9159b88f3b9/templates/two_stage.summarize.liquid b/src/Plugins/BotSharp.Plugin.Planner/data/agents/282a7128-69a1-44b0-878c-a9159b88f3b9/templates/two_stage.summarize.liquid
index a585cd23..c53abdab 100644
--- a/src/Plugins/BotSharp.Plugin.Planner/data/agents/282a7128-69a1-44b0-878c-a9159b88f3b9/templates/two_stage.summarize.liquid
+++ b/src/Plugins/BotSharp.Plugin.Planner/data/agents/282a7128-69a1-44b0-878c-a9159b88f3b9/templates/two_stage.summarize.liquid
@@ -1,4 +1,4 @@
-You are a planning summarizer. You will generate the final output in JSON format based on the task description, knowledge and related table structure and relationship.
+You are a planning summarizer. You will generate the final output in JSON format with short explanation based on the task description, knowledge and related table structure and relationship.
Requirements:
{{ summary_requirements }}
diff --git a/src/Plugins/BotSharp.Plugin.SqlDriver/Functions/ExecuteQueryFn.cs b/src/Plugins/BotSharp.Plugin.SqlDriver/Functions/ExecuteQueryFn.cs
index af337bab..99060105 100644
--- a/src/Plugins/BotSharp.Plugin.SqlDriver/Functions/ExecuteQueryFn.cs
+++ b/src/Plugins/BotSharp.Plugin.SqlDriver/Functions/ExecuteQueryFn.cs
@@ -30,7 +30,7 @@ public class ExecuteQueryFn : IFunctionCallback
public async Task Execute(RoleDialogModel message)
{
var args = JsonSerializer.Deserialize(message.FunctionArgs);
- var refinedArgs = await RefineSqlStatement(message, args);
+ //var refinedArgs = await RefineSqlStatement(message, args);
var dbHook = _services.GetRequiredService();
var dbType = dbHook.GetDatabaseType(message);
@@ -38,13 +38,13 @@ public class ExecuteQueryFn : IFunctionCallback
{
var results = dbType.ToLower() switch
{
- "mysql" => RunQueryInMySql(refinedArgs.SqlStatements),
- "sqlserver" => RunQueryInSqlServer(refinedArgs.SqlStatements),
- "redshift" => RunQueryInRedshift(refinedArgs.SqlStatements),
+ "mysql" => RunQueryInMySql(args.SqlStatements),
+ "sqlserver" => RunQueryInSqlServer(args.SqlStatements),
+ "redshift" => RunQueryInRedshift(args.SqlStatements),
_ => throw new NotImplementedException($"Database type {dbType} is not supported.")
};
- if (refinedArgs.SqlStatements.Length == 1 && refinedArgs.SqlStatements[0].StartsWith("DROP TABLE"))
+ if (args.SqlStatements.Length == 1 && args.SqlStatements[0].StartsWith("DROP TABLE"))
{
message.Content = "Drop table successfully";
return true;
diff --git a/src/Plugins/BotSharp.Plugin.SqlDriver/Functions/SqlValidateFn.cs b/src/Plugins/BotSharp.Plugin.SqlDriver/Functions/SqlValidateFn.cs
index a1c4dca9..5f33a74a 100644
--- a/src/Plugins/BotSharp.Plugin.SqlDriver/Functions/SqlValidateFn.cs
+++ b/src/Plugins/BotSharp.Plugin.SqlDriver/Functions/SqlValidateFn.cs
@@ -41,7 +41,7 @@ public class SqlValidateFn : IFunctionCallback
var dbType = dbHook.GetDatabaseType(message);
var validateSql = dbType.ToLower() switch
{
- "mysql" => $"explain\r\n{sql}",
+ "mysql" => $"explain\r\n{sql.Replace("SET ", "-- SET ", StringComparison.InvariantCultureIgnoreCase).Replace(";", "; explain ").TrimEnd("explain ".ToCharArray())}",
"sqlserver" => $"SET PARSEONLY ON;\r\n{sql}\r\nSET PARSEONLY OFF;",
"redshift" => $"explain\r\n{sql}",
_ => throw new NotImplementedException($"Database type {dbType} is not supported.")
@@ -49,7 +49,7 @@ public class SqlValidateFn : IFunctionCallback
var msgCopy = RoleDialogModel.From(message);
msgCopy.FunctionArgs = JsonSerializer.Serialize(new ExecuteQueryArgs
{
- SqlStatements = new string[] { validateSql }
+ SqlStatements = [validateSql]
});
var fn = _services.GetRequiredService();
@@ -74,7 +74,7 @@ public class SqlValidateFn : IFunctionCallback
Message = "Correct SQL Statement",
Data = new Dictionary
{
- { "original_sql", sql },
+ { "original_sql", message.Content },
{ "error_message", ex.Message },
{ "table_structure", ddl }
}
diff --git a/src/Plugins/BotSharp.Plugin.SqlDriver/data/agents/6745151e-6d46-4a02-8de4-1c4f21c7da95/functions/verify_dictionary_term.json b/src/Plugins/BotSharp.Plugin.SqlDriver/data/agents/6745151e-6d46-4a02-8de4-1c4f21c7da95/functions/verify_dictionary_term.json
index 871210a2..64502c62 100644
--- a/src/Plugins/BotSharp.Plugin.SqlDriver/data/agents/6745151e-6d46-4a02-8de4-1c4f21c7da95/functions/verify_dictionary_term.json
+++ b/src/Plugins/BotSharp.Plugin.SqlDriver/data/agents/6745151e-6d46-4a02-8de4-1c4f21c7da95/functions/verify_dictionary_term.json
@@ -1,6 +1,6 @@
{
"name": "verify_dictionary_term",
- "description": "Get id from dictionary table by keyword. Call this function only if need_lookup_dictionary is true and is_insert is false. You can only query one table at a time.",
+ "description": "Get id from dictionary table by keyword. Call this function only if need_lookup_dictionary is true, is_table_from_knowledge is true and is_insert is false. You can only query one table at a time. The table name must come from the global/domain knowledge.",
"parameters": {
"type": "object",
"properties": {
@@ -16,9 +16,13 @@
"type": "boolean",
"description": "if SQL statement is inserting."
},
+ "is_table_from_knowledge": {
+ "type": "boolean",
+ "description": "if table is from the global/domain knowledge."
+ },
"tables": {
"type": "array",
- "description": "all related dictionary tables must be from related knowledge in the context",
+ "description": "all related dictionary tables must be from global/domain knowledge in the context",
"items": {
"type": "string",
"description": "table name from related knowledge in the context"
diff --git a/src/Plugins/BotSharp.Plugin.SqlDriver/data/agents/beda4c12-e1ec-4b4b-b328-3df4a6687c4f/templates/database.summarize.mysql.liquid b/src/Plugins/BotSharp.Plugin.SqlDriver/data/agents/beda4c12-e1ec-4b4b-b328-3df4a6687c4f/templates/database.summarize.mysql.liquid
index 515deffd..1098bdd1 100644
--- a/src/Plugins/BotSharp.Plugin.SqlDriver/data/agents/beda4c12-e1ec-4b4b-b328-3df4a6687c4f/templates/database.summarize.mysql.liquid
+++ b/src/Plugins/BotSharp.Plugin.SqlDriver/data/agents/beda4c12-e1ec-4b4b-b328-3df4a6687c4f/templates/database.summarize.mysql.liquid
@@ -4,7 +4,7 @@ If not, generate the query step by step based on the planning.
The query must exactly based on the provided table structure. And carefully review the foreign keys to make sure you include all the accurate information.
-Note: Output should be only the sql query with sql comments that can be directly run in mysql database with version 8.0.
+Note: Output should be only the sql query with short sql comments and explanation that can be directly run in mysql database with version 8.0.
Don't use the sql statement that specify target table for update in FROM clause.
For example, you CAN'T write query as below:
diff --git a/src/Plugins/BotSharp.Plugin.SqlDriver/data/agents/beda4c12-e1ec-4b4b-b328-3df4a6687c4f/templates/sql_statement_correctness.liquid b/src/Plugins/BotSharp.Plugin.SqlDriver/data/agents/beda4c12-e1ec-4b4b-b328-3df4a6687c4f/templates/sql_statement_correctness.liquid
index 2e6f28e1..f180e19f 100644
--- a/src/Plugins/BotSharp.Plugin.SqlDriver/data/agents/beda4c12-e1ec-4b4b-b328-3df4a6687c4f/templates/sql_statement_correctness.liquid
+++ b/src/Plugins/BotSharp.Plugin.SqlDriver/data/agents/beda4c12-e1ec-4b4b-b328-3df4a6687c4f/templates/sql_statement_correctness.liquid
@@ -1,5 +1,5 @@
You are a sql statement corrector. You will need to refer to the table structure and rewrite the original sql statement so it's using the correct information, e.g. column name.
-Output the sql statement only without comments, in JSON format: {{ response_format }}
+Correct the sql statement and keep only the original explanation and comments without any information related to error message{% if response_format %} in JSON format: {{ response_format }} {% endif %}.
Make sure all the column names are defined in the Table Structure.
=====