Merge branch 'master' into lida_Dev
This commit is contained in:
commit
54b0381692
11
BotSharp.sln
11
BotSharp.sln
|
|
@ -117,6 +117,8 @@ 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}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
|
|
@ -469,6 +471,14 @@ Global
|
|||
{F57F4862-F8D4-44A1-AC12-5C131B5C9785}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{F57F4862-F8D4-44A1-AC12-5C131B5C9785}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{F57F4862-F8D4-44A1-AC12-5C131B5C9785}.Release|x64.Build.0 = Release|Any CPU
|
||||
{6D3A54F9-4792-41DB-BE7D-4F7B1D918EAE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{6D3A54F9-4792-41DB-BE7D-4F7B1D918EAE}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{6D3A54F9-4792-41DB-BE7D-4F7B1D918EAE}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{6D3A54F9-4792-41DB-BE7D-4F7B1D918EAE}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{6D3A54F9-4792-41DB-BE7D-4F7B1D918EAE}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{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
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
|
|
@ -525,6 +535,7 @@ Global
|
|||
{97A0B191-64D7-4F8A-BFE8-1BFCC5E247E1} = {2635EC9B-2E5F-4313-AC21-0B847F31F36C}
|
||||
{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}
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {A9969D89-C98B-40A5-A12B-FC87E55B3A19}
|
||||
|
|
|
|||
|
|
@ -55,7 +55,7 @@ public interface IAgentService
|
|||
string GetDataDir();
|
||||
string GetAgentDataDir(string agentId);
|
||||
|
||||
List<Agent> GetAgentsByUser(string userId);
|
||||
Task<List<UserAgent>> GetUserAgents(string userId);
|
||||
|
||||
PluginDef GetPlugin(string agentId);
|
||||
|
||||
|
|
|
|||
|
|
@ -1,11 +1,27 @@
|
|||
using BotSharp.Abstraction.Users.Models;
|
||||
|
||||
namespace BotSharp.Abstraction.Agents.Models;
|
||||
|
||||
public class UserAgent
|
||||
{
|
||||
[JsonPropertyName("id")]
|
||||
public string Id { get; set; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("user_id")]
|
||||
public string UserId { get; set; } = string.Empty;
|
||||
public string AgentId { get; set; } = string.Empty;
|
||||
public bool Editable { get; set; }
|
||||
|
||||
[JsonPropertyName("agent_id")]
|
||||
public string AgentId { get; set; }
|
||||
|
||||
[JsonPropertyName("actions")]
|
||||
public IEnumerable<string> Actions { get; set; } = [];
|
||||
|
||||
[JsonIgnore]
|
||||
public Agent? Agent { get; set; }
|
||||
|
||||
[JsonPropertyName("updated_time")]
|
||||
public DateTime UpdatedTime { get; set; } = DateTime.UtcNow;
|
||||
|
||||
[JsonPropertyName("created_time")]
|
||||
public DateTime CreatedTime { get; set; } = DateTime.UtcNow;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>$(TargetFramework)</TargetFramework>
|
||||
|
|
@ -38,6 +38,7 @@
|
|||
<PackageReference Include="Serilog.Sinks.Console" Version="5.0.1" />
|
||||
<PackageReference Include="Serilog.Sinks.File" Version="5.0.0" />
|
||||
<PackageReference Include="Rougamo.Fody" Version="4.0.0" />
|
||||
<PackageReference Include="AspectInjector" Version="2.8.2" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
|
|||
|
|
@ -19,4 +19,8 @@ public interface IConversationStateService
|
|||
bool RemoveState(string name);
|
||||
void CleanStates(params string[] excludedStates);
|
||||
void Save();
|
||||
|
||||
ConversationState GetCurrentState();
|
||||
void SetCurrentState(ConversationState state);
|
||||
void ResetCurrentState();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,10 @@
|
|||
namespace BotSharp.Abstraction.Conversations.Models;
|
||||
|
||||
public class ConversationContext
|
||||
{
|
||||
public ConversationState State { get; set; }
|
||||
public List<DialogElement> Dialogs { get; set; } = new();
|
||||
public List<ConversationBreakpoint> Breakpoints { get; set; } = new();
|
||||
public int RecursiveCounter { get; set; }
|
||||
public Stack<string> RoutingStack { get; set; } = new();
|
||||
}
|
||||
|
|
@ -3,7 +3,7 @@ namespace BotSharp.Abstraction.Files.Models;
|
|||
public class FileInformation
|
||||
{
|
||||
/// <summary>
|
||||
/// External file url
|
||||
/// External file url for display
|
||||
/// </summary>
|
||||
[JsonPropertyName("file_url")]
|
||||
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
||||
|
|
@ -35,4 +35,11 @@ public class FileInformation
|
|||
[JsonPropertyName("file_extension")]
|
||||
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
||||
public string? FileExtension { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// External file url for download
|
||||
/// </summary>
|
||||
[JsonPropertyName("file_download_url")]
|
||||
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
||||
public string? FileDownloadUrl { get; set; } = string.Empty;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
using BotSharp.Abstraction.Loggers.Models;
|
||||
using BotSharp.Abstraction.Plugins.Models;
|
||||
using BotSharp.Abstraction.Repositories.Filters;
|
||||
using BotSharp.Abstraction.Shared;
|
||||
using BotSharp.Abstraction.Tasks.Models;
|
||||
using BotSharp.Abstraction.Translation.Models;
|
||||
using BotSharp.Abstraction.Users.Models;
|
||||
|
|
@ -8,11 +9,8 @@ using BotSharp.Abstraction.VectorStorage.Models;
|
|||
|
||||
namespace BotSharp.Abstraction.Repositories;
|
||||
|
||||
public interface IBotSharpRepository
|
||||
public interface IBotSharpRepository : IHaveServiceProvider
|
||||
{
|
||||
int Transaction<TTableInterface>(Action action);
|
||||
void Add<TTableInterface>(object entity);
|
||||
|
||||
#region Plugin
|
||||
PluginConfig GetPluginConfig();
|
||||
void SavePluginConfig(PluginConfig config);
|
||||
|
|
@ -35,13 +33,15 @@ public interface IBotSharpRepository
|
|||
void UpdateUserPhone(string userId, string Iphone, string regionCode) => throw new NotImplementedException();
|
||||
void UpdateUserIsDisable(string userId, bool isDisable) => throw new NotImplementedException();
|
||||
void UpdateUsersIsDisable(List<string> userIds, bool isDisable) => throw new NotImplementedException();
|
||||
PagedItems<User> GetUsers(UserFilter filter) => throw new NotImplementedException();
|
||||
bool UpdateUser(User user, bool isUpdateUserAgents = false) => throw new NotImplementedException();
|
||||
#endregion
|
||||
|
||||
#region Agent
|
||||
void UpdateAgent(Agent agent, AgentField field);
|
||||
Agent? GetAgent(string agentId);
|
||||
List<Agent> GetAgents(AgentFilter filter);
|
||||
List<Agent> GetAgentsByUser(string userId);
|
||||
List<UserAgent> GetUserAgents(string userId);
|
||||
void BulkInsertAgents(List<Agent> agents);
|
||||
void BulkInsertUserAgents(List<UserAgent> userAgents);
|
||||
bool DeleteAgents();
|
||||
|
|
|
|||
|
|
@ -17,4 +17,15 @@ public interface IRoutingContext
|
|||
void PopTo(string agentId, string reason);
|
||||
void Replace(string agentId, string? reason = null);
|
||||
void Empty(string? reason = null);
|
||||
|
||||
|
||||
int CurrentRecursionDepth { get; }
|
||||
int GetRecursiveCounter();
|
||||
void IncreaseRecursiveCounter();
|
||||
void SetRecursiveCounter(int counter);
|
||||
void ResetRecursiveCounter();
|
||||
|
||||
Stack<string> GetAgentStack();
|
||||
void SetAgentStack(Stack<string> stack);
|
||||
void ResetAgentStack();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,7 +27,11 @@ public interface IRoutingService
|
|||
RoutingRule[] GetRulesByAgentId(string id);
|
||||
|
||||
List<RoutingHandlerDef> GetHandlers(Agent router);
|
||||
void ResetRecursiveCounter();
|
||||
|
||||
//void ResetRecursiveCounter();
|
||||
//int GetRecursiveCounter();
|
||||
//void SetRecursiveCounter(int counter);
|
||||
|
||||
Task<bool> InvokeAgent(string agentId, List<RoleDialogModel> dialogs);
|
||||
Task<bool> InvokeFunction(string name, RoleDialogModel messages);
|
||||
Task<RoleDialogModel> InstructLoop(RoleDialogModel message, List<RoleDialogModel> dialogs);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,6 @@
|
|||
namespace BotSharp.Abstraction.Shared;
|
||||
|
||||
public interface IHaveServiceProvider
|
||||
{
|
||||
IServiceProvider ServiceProvider { get; }
|
||||
}
|
||||
|
|
@ -0,0 +1,168 @@
|
|||
using AspectInjector.Broker;
|
||||
using BotSharp.Abstraction.Shared;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using System.Reflection;
|
||||
|
||||
namespace BotSharp.Abstraction.SideCar.Attributes;
|
||||
|
||||
[Aspect(Scope.PerInstance)]
|
||||
public class SideCarAspect
|
||||
{
|
||||
[Advice(Kind.Around)]
|
||||
public object Handle(
|
||||
[Argument(Source.Target)] Func<object[], object> target,
|
||||
[Argument(Source.Arguments)] object[] args,
|
||||
[Argument(Source.Instance)] object instance,
|
||||
[Argument(Source.ReturnType)] Type retType,
|
||||
[Argument(Source.Name)] string name,
|
||||
[Argument(Source.Metadata)] MethodBase metaData,
|
||||
[Argument(Source.Triggers)] Attribute[] triggers)
|
||||
{
|
||||
object value;
|
||||
var serviceProvider = ((IHaveServiceProvider)instance).ServiceProvider;
|
||||
|
||||
if (typeof(Task).IsAssignableFrom(retType))
|
||||
{
|
||||
var syncResultType = retType.IsConstructedGenericType ? retType.GenericTypeArguments[0] : typeof(void);
|
||||
value = CallAsyncMethod(serviceProvider, syncResultType, name, target, args);
|
||||
}
|
||||
else
|
||||
{
|
||||
value = CallSyncMethod(serviceProvider, retType, name, target, args);
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
|
||||
private static MethodInfo GetMethod(string name)
|
||||
{
|
||||
return typeof(SideCarAspect).GetMethod(name, BindingFlags.NonPublic | BindingFlags.Static);
|
||||
}
|
||||
|
||||
private object CallAsyncMethod(IServiceProvider serviceProvider, Type retType, string methodName, Func<object[], object> target, object[] args)
|
||||
{
|
||||
var sidecar = serviceProvider.GetService<IConversationSideCar>();
|
||||
var sidecarMethod = sidecar?.GetType()?.GetMethod(methodName, BindingFlags.Public | BindingFlags.Instance);
|
||||
|
||||
object value;
|
||||
var enabled = sidecar != null && sidecar.IsEnabled() && sidecarMethod != null;
|
||||
|
||||
if (retType == typeof(void))
|
||||
{
|
||||
if (enabled)
|
||||
{
|
||||
|
||||
value = GetMethod(nameof(CallAsync)).Invoke(this, [sidecar, sidecarMethod, args]);
|
||||
}
|
||||
else
|
||||
{
|
||||
value = GetMethod(nameof(WrapAsync)).Invoke(this, [target, args]);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (enabled)
|
||||
{
|
||||
value = GetMethod(nameof(CallGenericAsync)).MakeGenericMethod(retType).Invoke(this, [sidecar, sidecarMethod, args]);
|
||||
}
|
||||
else
|
||||
{
|
||||
value = GetMethod(nameof(WrapGenericAsync)).MakeGenericMethod(retType).Invoke(this, [target, args]);
|
||||
}
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
private object CallSyncMethod(IServiceProvider serviceProvider, Type retType, string methodName, Func<object[], object> target, object[] args)
|
||||
{
|
||||
var sidecar = serviceProvider.GetService<IConversationSideCar>();
|
||||
var sidecarMethod = sidecar?.GetType()?.GetMethod(methodName, BindingFlags.Public | BindingFlags.Instance);
|
||||
|
||||
object value;
|
||||
var enabled = sidecar != null && sidecarMethod != null && sidecar.IsEnabled();
|
||||
|
||||
if (retType == typeof(void))
|
||||
{
|
||||
if (enabled)
|
||||
{
|
||||
value = GetMethod(nameof(CallSync)).Invoke(this, [sidecar, sidecarMethod, args]);
|
||||
}
|
||||
else
|
||||
{
|
||||
value = GetMethod(nameof(WrapSync)).Invoke(this, [target, args]);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (enabled)
|
||||
{
|
||||
value = GetMethod(nameof(CallGenericSync)).MakeGenericMethod(retType).Invoke(this, [sidecar, sidecarMethod, args]);
|
||||
}
|
||||
else
|
||||
{
|
||||
value = GetMethod(nameof(WrapGenericSync)).MakeGenericMethod(retType).Invoke(this, [target, args]);
|
||||
}
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
|
||||
#region Call Side car method
|
||||
private static async Task<T> CallGenericAsync<T>(object instance, MethodInfo method, object[] args)
|
||||
{
|
||||
var res = await (Task<T>)method.Invoke(instance, args);
|
||||
return res;
|
||||
}
|
||||
|
||||
private static async Task CallAsync(object instance, MethodInfo method, object[] args)
|
||||
{
|
||||
await (Task)method.Invoke(instance, args);
|
||||
return;
|
||||
}
|
||||
|
||||
private static T CallGenericSync<T>(object instance, MethodInfo method, object[] args)
|
||||
{
|
||||
var res = (T)method.Invoke(instance, args);
|
||||
return res;
|
||||
}
|
||||
|
||||
private static void CallSync(object instance, MethodInfo method, object[] args)
|
||||
{
|
||||
method.Invoke(instance, args);
|
||||
return;
|
||||
}
|
||||
#endregion
|
||||
|
||||
|
||||
#region Call original method
|
||||
private static T WrapGenericSync<T>(Func<object[], object> target, object[] args)
|
||||
{
|
||||
T res;
|
||||
res = (T)target(args);
|
||||
return res;
|
||||
}
|
||||
|
||||
private static async Task<T> WrapGenericAsync<T>(Func<object[], object> target, object[] args)
|
||||
{
|
||||
T res;
|
||||
res = await (Task<T>)target(args);
|
||||
return res;
|
||||
}
|
||||
|
||||
|
||||
private static void WrapSync(Func<object[], object> target, object[] args)
|
||||
{
|
||||
target(args);
|
||||
return;
|
||||
}
|
||||
|
||||
private static async Task WrapAsync(Func<object[], object> target, object[] args)
|
||||
{
|
||||
await (Task)target(args);
|
||||
return;
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
using AspectInjector.Broker;
|
||||
|
||||
namespace BotSharp.Abstraction.SideCar.Attributes;
|
||||
|
||||
[AttributeUsage(AttributeTargets.Method, Inherited = true)]
|
||||
[Injection(typeof(SideCarAspect))]
|
||||
public class SideCarAttribute : Attribute
|
||||
{
|
||||
public SideCarAttribute()
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
namespace BotSharp.Abstraction.SideCar;
|
||||
|
||||
public interface IConversationSideCar
|
||||
{
|
||||
string Provider { get; }
|
||||
|
||||
bool IsEnabled();
|
||||
void AppendConversationDialogs(string conversationId, List<DialogElement> messages);
|
||||
List<DialogElement> GetConversationDialogs(string conversationId);
|
||||
void UpdateConversationBreakpoint(string conversationId, ConversationBreakpoint breakpoint);
|
||||
ConversationBreakpoint? GetConversationBreakpoint(string conversationId);
|
||||
Task<RoleDialogModel> SendMessage(string agentId, string text, PostbackMessageModel? postback = null, List<MessageState>? states = null);
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
namespace BotSharp.Abstraction.Users.Enums;
|
||||
|
||||
public static class UserAction
|
||||
{
|
||||
public const string Edit = "edit";
|
||||
public const string Chat = "chat";
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
namespace BotSharp.Abstraction.Users.Enums;
|
||||
|
||||
public static class UserConstant
|
||||
{
|
||||
public static IEnumerable<string> AdminRoles = new List<string>
|
||||
{
|
||||
UserRole.Admin,
|
||||
UserRole.Root
|
||||
};
|
||||
}
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
namespace BotSharp.Abstraction.Users.Enums;
|
||||
|
||||
public static class UserPermission
|
||||
{
|
||||
public const string CreateAgent = "create-agent";
|
||||
}
|
||||
|
|
@ -35,4 +35,4 @@ public class UserRole
|
|||
public const string Assistant = "assistant";
|
||||
|
||||
public const string Root = "root";
|
||||
}
|
||||
}
|
||||
|
|
@ -13,8 +13,6 @@ public interface IUserIdentity
|
|||
/// </summary>
|
||||
string UserLanguage { get; }
|
||||
string? Phone { get; }
|
||||
string? AffiliateId { get; }
|
||||
string? EmployeeId { get; }
|
||||
string Type { get; }
|
||||
string Role { get; }
|
||||
string? RegionCode { get; }
|
||||
|
|
|
|||
|
|
@ -6,6 +6,8 @@ namespace BotSharp.Abstraction.Users;
|
|||
public interface IUserService
|
||||
{
|
||||
Task<User> GetUser(string id);
|
||||
Task<PagedItems<User>> GetUsers(UserFilter filter);
|
||||
Task<bool> UpdateUser(User model, bool isUpdateUserAgents = false);
|
||||
Task<User> CreateUser(User user);
|
||||
Task<Token> ActiveUser(UserActivationModel model);
|
||||
Task<Token?> GetAffiliateToken(string authorization);
|
||||
|
|
|
|||
|
|
@ -25,6 +25,10 @@ public class User
|
|||
public string? AffiliateId { get; set; }
|
||||
public string? EmployeeId { get; set; }
|
||||
public bool IsDisabled { get; set; }
|
||||
public IEnumerable<string> Permissions { get; set; } = [];
|
||||
|
||||
[JsonIgnore]
|
||||
public IEnumerable<UserAgentAction> AgentActions { get; set; } = [];
|
||||
public DateTime UpdatedTime { get; set; } = DateTime.UtcNow;
|
||||
public DateTime CreatedTime { get; set; } = DateTime.UtcNow;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,16 @@
|
|||
namespace BotSharp.Abstraction.Users.Models;
|
||||
|
||||
public class UserAgentAction
|
||||
{
|
||||
[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<string> Actions { get; set; } = [];
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
namespace BotSharp.Abstraction.Users.Models;
|
||||
|
||||
public class UserFilter : Pagination
|
||||
{
|
||||
[JsonPropertyName("user_ids")]
|
||||
public IEnumerable<string>? UserIds { get; set; }
|
||||
|
||||
[JsonPropertyName("user_names")]
|
||||
public IEnumerable<string>? UserNames { get; set; }
|
||||
|
||||
[JsonPropertyName("external_ids")]
|
||||
public IEnumerable<string>? ExternalIds { get; set; }
|
||||
|
||||
[JsonPropertyName("roles")]
|
||||
public IEnumerable<string>? Roles { get; set; }
|
||||
|
||||
[JsonPropertyName("sources")]
|
||||
public IEnumerable<string>? Sources { get; set; }
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>$(TargetFramework)</TargetFramework>
|
||||
<LangVersion>$(LangVersion)</LangVersion>
|
||||
<VersionPrefix>$(BotSharpVersion)</VersionPrefix>
|
||||
<GeneratePackageOnBuild>$(GeneratePackageOnBuild)</GeneratePackageOnBuild>
|
||||
<OutputPath>$(SolutionDir)packages</OutputPath>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\BotSharp.Abstraction\BotSharp.Abstraction.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
using BotSharp.Abstraction.Plugins;
|
||||
using BotSharp.Abstraction.Settings;
|
||||
using BotSharp.Core.SideCar.Services;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
|
||||
namespace BotSharp.Core.SideCar;
|
||||
|
||||
public class BotSharpSideCarPlugin : IBotSharpPlugin
|
||||
{
|
||||
public string Id => "06e5a276-bba0-45af-9625-889267c341c9";
|
||||
public string Name => "Side car";
|
||||
public string Description => "Provides side car for calling agent cluster in conversation";
|
||||
|
||||
public SettingsMeta Settings => new SettingsMeta("SideCar");
|
||||
public object GetNewSettingsInstance() => new SideCarSettings();
|
||||
|
||||
public void RegisterDI(IServiceCollection services, IConfiguration config)
|
||||
{
|
||||
var settings = new SideCarSettings();
|
||||
config.Bind("SideCar", settings);
|
||||
services.AddSingleton(settings);
|
||||
|
||||
if (settings.Conversation.Provider == "botsharp")
|
||||
{
|
||||
services.AddScoped<IConversationSideCar, BotSharpConversationSideCar>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,135 @@
|
|||
namespace BotSharp.Core.SideCar.Services;
|
||||
|
||||
public class BotSharpConversationSideCar : IConversationSideCar
|
||||
{
|
||||
private readonly IServiceProvider _services;
|
||||
private readonly ILogger<BotSharpConversationSideCar> _logger;
|
||||
|
||||
private Stack<ConversationContext> contextStack = new();
|
||||
|
||||
private bool enabled = false;
|
||||
|
||||
public string Provider => "botsharp";
|
||||
|
||||
public BotSharpConversationSideCar(
|
||||
IServiceProvider services,
|
||||
ILogger<BotSharpConversationSideCar> logger)
|
||||
{
|
||||
_services = services;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public bool IsEnabled()
|
||||
{
|
||||
return enabled;
|
||||
}
|
||||
|
||||
public void AppendConversationDialogs(string conversationId, List<DialogElement> messages)
|
||||
{
|
||||
if (contextStack.IsNullOrEmpty()) return;
|
||||
|
||||
var top = contextStack.Peek();
|
||||
top.Dialogs.AddRange(messages);
|
||||
}
|
||||
|
||||
public List<DialogElement> GetConversationDialogs(string conversationId)
|
||||
{
|
||||
if (contextStack.IsNullOrEmpty())
|
||||
{
|
||||
return new List<DialogElement>();
|
||||
}
|
||||
|
||||
return contextStack.Peek().Dialogs;
|
||||
}
|
||||
|
||||
public void UpdateConversationBreakpoint(string conversationId, ConversationBreakpoint breakpoint)
|
||||
{
|
||||
if (contextStack.IsNullOrEmpty()) return;
|
||||
|
||||
var top = contextStack.Peek().Breakpoints;
|
||||
top.Add(breakpoint);
|
||||
}
|
||||
|
||||
public ConversationBreakpoint? GetConversationBreakpoint(string conversationId)
|
||||
{
|
||||
if (contextStack.IsNullOrEmpty())
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var top = contextStack.Peek().Breakpoints;
|
||||
return top.LastOrDefault();
|
||||
}
|
||||
|
||||
public async Task<RoleDialogModel> SendMessage(string agentId, string text,
|
||||
PostbackMessageModel? postback = null, List<MessageState>? states = null)
|
||||
{
|
||||
BeforeExecute();
|
||||
var response = await InnerExecute(agentId, text, postback, states);
|
||||
AfterExecute();
|
||||
return response;
|
||||
}
|
||||
|
||||
private async Task<RoleDialogModel> InnerExecute(string agentId, string text,
|
||||
PostbackMessageModel? postback = null, List<MessageState>? states = null)
|
||||
{
|
||||
var conv = _services.GetRequiredService<IConversationService>();
|
||||
var routing = _services.GetRequiredService<IRoutingService>();
|
||||
var state = _services.GetRequiredService<IConversationStateService>();
|
||||
|
||||
var inputMsg = new RoleDialogModel(AgentRole.User, text);
|
||||
routing.Context.SetMessageId(conv.ConversationId, inputMsg.MessageId);
|
||||
states?.ForEach(x => state.SetState(x.Key, x.Value, activeRounds: x.ActiveRounds, source: StateSource.External));
|
||||
|
||||
var response = new RoleDialogModel(AgentRole.Assistant, string.Empty);
|
||||
await conv.SendMessage(agentId, inputMsg,
|
||||
replyMessage: postback,
|
||||
async msg =>
|
||||
{
|
||||
response.Content = !string.IsNullOrEmpty(msg.SecondaryContent) ? msg.SecondaryContent : msg.Content;
|
||||
response.FunctionName = msg.FunctionName;
|
||||
response.RichContent = msg.SecondaryRichContent ?? msg.RichContent;
|
||||
response.Instruction = msg.Instruction;
|
||||
response.Data = msg.Data;
|
||||
});
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
private void BeforeExecute()
|
||||
{
|
||||
enabled = true;
|
||||
var state = _services.GetRequiredService<IConversationStateService>();
|
||||
var routing = _services.GetRequiredService<IRoutingService>();
|
||||
|
||||
var node = new ConversationContext
|
||||
{
|
||||
State = state.GetCurrentState(),
|
||||
Dialogs = new(),
|
||||
Breakpoints = new(),
|
||||
RecursiveCounter = routing.Context.GetRecursiveCounter(),
|
||||
RoutingStack = routing.Context.GetAgentStack()
|
||||
};
|
||||
contextStack.Push(node);
|
||||
|
||||
// Reset
|
||||
state.ResetCurrentState();
|
||||
routing.Context.ResetRecursiveCounter();
|
||||
routing.Context.ResetAgentStack();
|
||||
|
||||
}
|
||||
|
||||
private void AfterExecute()
|
||||
{
|
||||
var state = _services.GetRequiredService<IConversationStateService>();
|
||||
var routing = _services.GetRequiredService<IRoutingService>();
|
||||
|
||||
var node = contextStack.Pop();
|
||||
|
||||
// Recover
|
||||
state.SetCurrentState(node.State);
|
||||
routing.Context.SetRecursiveCounter(node.RecursiveCounter);
|
||||
routing.Context.SetAgentStack(node.RoutingStack);
|
||||
enabled = false;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
namespace BotSharp.Core.SideCar.Settings;
|
||||
|
||||
public class SideCarSettings
|
||||
{
|
||||
public BaseSetting Conversation { get; set; }
|
||||
}
|
||||
|
||||
public class BaseSetting
|
||||
{
|
||||
public string Provider { get; set; }
|
||||
}
|
||||
20
src/Infrastructure/BotSharp.Core.SideCar/Using.cs
Normal file
20
src/Infrastructure/BotSharp.Core.SideCar/Using.cs
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
global using System;
|
||||
global using System.Collections.Generic;
|
||||
global using System.Text;
|
||||
global using System.Threading.Tasks;
|
||||
global using System.Linq;
|
||||
global using System.Text.Json;
|
||||
global using System.Net.Mime;
|
||||
global using System.Net.Http;
|
||||
global using System.Threading;
|
||||
global using Microsoft.Extensions.DependencyInjection;
|
||||
global using Microsoft.Extensions.Logging;
|
||||
global using BotSharp.Abstraction.Agents.Enums;
|
||||
global using BotSharp.Abstraction.Conversations;
|
||||
global using BotSharp.Abstraction.Conversations.Enums;
|
||||
global using BotSharp.Abstraction.Conversations.Models;
|
||||
global using BotSharp.Abstraction.Models;
|
||||
global using BotSharp.Abstraction.Routing;
|
||||
global using BotSharp.Abstraction.SideCar;
|
||||
global using BotSharp.Abstraction.Utilities;
|
||||
global using BotSharp.Core.SideCar.Settings;
|
||||
|
|
@ -45,7 +45,7 @@ public class AgentPlugin : IBotSharpPlugin
|
|||
SubMenu = new List<PluginMenuDef>
|
||||
{
|
||||
new PluginMenuDef("Routing", link: "page/agent/router"), // icon: "bx bx-map-pin"
|
||||
new PluginMenuDef("Evaluating", link: "page/agent/evaluator") { Roles = new List<string> { UserRole.Admin } }, // icon: "bx bx-task"
|
||||
new PluginMenuDef("Evaluating", link: "page/agent/evaluator") { Roles = new List<string> { UserRole.Root, UserRole.Admin } }, // icon: "bx bx-task"
|
||||
new PluginMenuDef("Agents", link: "page/agent"), // icon: "bx bx-bot"
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
using BotSharp.Abstraction.Tasks.Models;
|
||||
using BotSharp.Abstraction.Users.Enums;
|
||||
using System.IO;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
|
|
@ -8,14 +9,14 @@ public partial class AgentService
|
|||
{
|
||||
public async Task<Agent> CreateAgent(Agent agent)
|
||||
{
|
||||
var agentRecord = _db.GetAgentsByUser(_user.Id).FirstOrDefault(x => x.Name.IsEqualTo(agent.Name));
|
||||
|
||||
if (agentRecord != null)
|
||||
var userAgents = _db.GetUserAgents(_user.Id);
|
||||
var found = userAgents?.FirstOrDefault(x => x.Agent != null && x.Agent.Name.IsEqualTo(agent.Name));
|
||||
if (found != null)
|
||||
{
|
||||
return agentRecord;
|
||||
return found.Agent;
|
||||
}
|
||||
|
||||
agentRecord = Agent.Clone(agent);
|
||||
var agentRecord = Agent.Clone(agent);
|
||||
agentRecord.Id = Guid.NewGuid().ToString();
|
||||
agentRecord.CreatedDateTime = DateTime.UtcNow;
|
||||
agentRecord.UpdatedDateTime = DateTime.UtcNow;
|
||||
|
|
@ -24,21 +25,21 @@ public partial class AgentService
|
|||
var agentSettings = _services.GetRequiredService<AgentSettings>();
|
||||
|
||||
var user = _db.GetUserById(_user.Id);
|
||||
var userAgentRecord = new UserAgent
|
||||
_db.BulkInsertAgents(new List<Agent> { agentRecord });
|
||||
if (!UserConstant.AdminRoles.Contains(user.Role))
|
||||
{
|
||||
Id = Guid.NewGuid().ToString(),
|
||||
UserId = user.Id,
|
||||
AgentId = agentRecord.Id,
|
||||
Editable = false,
|
||||
CreatedTime = DateTime.UtcNow,
|
||||
UpdatedTime = DateTime.UtcNow
|
||||
};
|
||||
|
||||
_db.Transaction<IBotSharpTable>(delegate
|
||||
{
|
||||
_db.Add<IBotSharpTable>(agentRecord);
|
||||
_db.Add<IBotSharpTable>(userAgentRecord);
|
||||
});
|
||||
_db.BulkInsertUserAgents(new List<UserAgent>
|
||||
{
|
||||
new UserAgent
|
||||
{
|
||||
UserId = user.Id,
|
||||
AgentId = agentRecord.Id,
|
||||
Actions = new List<string> { UserAction.Edit, UserAction.Chat },
|
||||
CreatedTime = DateTime.UtcNow,
|
||||
UpdatedTime = DateTime.UtcNow
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Utilities.ClearCache();
|
||||
return await Task.FromResult(agentRecord);
|
||||
|
|
@ -213,17 +214,4 @@ public partial class AgentService
|
|||
task.Content = content.Substring(suffix.Length).Trim();
|
||||
return task;
|
||||
}
|
||||
|
||||
private UserAgent BuildUserAgent(string agentId, string userId, bool editable = false)
|
||||
{
|
||||
return new UserAgent
|
||||
{
|
||||
Id = Guid.NewGuid().ToString(),
|
||||
UserId = userId,
|
||||
AgentId = agentId,
|
||||
Editable = editable,
|
||||
CreatedTime = DateTime.UtcNow,
|
||||
UpdatedTime = DateTime.UtcNow
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,9 +7,10 @@ public partial class AgentService
|
|||
public async Task<bool> DeleteAgent(string id)
|
||||
{
|
||||
var user = _db.GetUserById(_user.Id);
|
||||
var agent = _db.GetAgentsByUser(_user.Id).FirstOrDefault(x => x.Id.IsEqualTo(id));
|
||||
var userAgents = await GetUserAgents(user?.Id);
|
||||
var found = userAgents?.FirstOrDefault(x => x.AgentId == id);
|
||||
|
||||
if (user?.Role != UserRole.Admin && agent == null)
|
||||
if (!UserConstant.AdminRoles.Contains(user?.Role) && (found?.Actions == null || !found.Actions.Contains(UserAction.Edit)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -54,7 +54,6 @@ public partial class AgentService
|
|||
.SetResponses(responses)
|
||||
.SetSamples(samples);
|
||||
|
||||
var userAgent = BuildUserAgent(agent.Id, user.Id);
|
||||
var tasks = GetTasksFromFile(dir);
|
||||
|
||||
var isAgentDeleted = _db.DeleteAgent(agent.Id);
|
||||
|
|
@ -62,7 +61,6 @@ public partial class AgentService
|
|||
{
|
||||
await Task.Delay(100);
|
||||
_db.BulkInsertAgents(new List<Agent> { agent });
|
||||
_db.BulkInsertUserAgents(new List<UserAgent> { userAgent });
|
||||
_db.BulkInsertAgentTasks(tasks);
|
||||
refreshedAgents.Add(agent.Name);
|
||||
_logger.LogInformation($"Agent {agent.Name} has been migrated.");
|
||||
|
|
|
|||
|
|
@ -9,13 +9,18 @@ public partial class AgentService
|
|||
{
|
||||
public async Task UpdateAgent(Agent agent, AgentField updateField)
|
||||
{
|
||||
if (agent == null || string.IsNullOrEmpty(agent.Id)) return;
|
||||
|
||||
var userService = _services.GetRequiredService<IUserService>();
|
||||
var user = await userService.GetUser(_user.Id);
|
||||
var userAgents = GetAgentsByUser(user?.Id);
|
||||
var editable = userAgents?.Select(x => x.Id)?.Contains(agent.Id) ?? false;
|
||||
if (user?.Role != UserRole.Admin && !editable) return;
|
||||
|
||||
if (agent == null || string.IsNullOrEmpty(agent.Id)) return;
|
||||
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)))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var record = _db.GetAgent(agent.Id);
|
||||
if (record == null) return;
|
||||
|
|
|
|||
|
|
@ -49,10 +49,12 @@ public partial class AgentService : IAgentService
|
|||
return dir;
|
||||
}
|
||||
|
||||
public List<Agent> GetAgentsByUser(string userId)
|
||||
public async Task<List<UserAgent>> GetUserAgents(string userId)
|
||||
{
|
||||
var agents = _db.GetAgentsByUser(userId);
|
||||
return agents;
|
||||
if (string.IsNullOrEmpty(userId)) return [];
|
||||
|
||||
var userAgents = _db.GetUserAgents(userId);
|
||||
return userAgents;
|
||||
}
|
||||
|
||||
public IEnumerable<string> GetAgentUtilities()
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
using BotSharp.Abstraction.Messaging;
|
||||
using BotSharp.Abstraction.Messaging.Models.RichContent;
|
||||
using BotSharp.Abstraction.Routing.Settings;
|
||||
using BotSharp.Core.Routing.Planning;
|
||||
|
||||
namespace BotSharp.Core.Conversations.Services;
|
||||
|
||||
|
|
@ -90,7 +89,7 @@ public partial class ConversationService
|
|||
response = await routing.InstructDirect(agent, message);
|
||||
}
|
||||
|
||||
routing.ResetRecursiveCounter();
|
||||
routing.Context.ResetRecursiveCounter();
|
||||
}
|
||||
|
||||
await HandleAssistantMessage(response, onMessageReceived);
|
||||
|
|
|
|||
|
|
@ -98,6 +98,7 @@ public partial class ConversationService : IConversationService
|
|||
var record = sess;
|
||||
record.Id = sess.Id.IfNullOrEmptyAs(Guid.NewGuid().ToString());
|
||||
record.UserId = sess.UserId.IfNullOrEmptyAs(foundUserId);
|
||||
record.Tags = sess.Tags;
|
||||
record.Title = "New Conversation";
|
||||
|
||||
db.CreateNewConversation(record);
|
||||
|
|
@ -141,6 +142,7 @@ public partial class ConversationService : IConversationService
|
|||
{
|
||||
var db = _services.GetRequiredService<IBotSharpRepository>();
|
||||
var breakpoint = db.GetConversationBreakpoint(_conversationId);
|
||||
|
||||
if (breakpoint != null)
|
||||
{
|
||||
dialogs = dialogs.Where(x => x.CreatedAt >= breakpoint.Breakpoint).ToList();
|
||||
|
|
@ -151,9 +153,7 @@ public partial class ConversationService : IConversationService
|
|||
}
|
||||
}
|
||||
|
||||
return dialogs
|
||||
.TakeLast(lastCount)
|
||||
.ToList();
|
||||
return dialogs.TakeLast(lastCount).ToList();
|
||||
}
|
||||
|
||||
public void SetConversationId(string conversationId, List<MessageState> states, bool isReadOnly = false)
|
||||
|
|
|
|||
|
|
@ -384,4 +384,23 @@ public class ConversationStateService : IConversationStateService, IDisposable
|
|||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public ConversationState GetCurrentState()
|
||||
{
|
||||
var values = _curStates.Values.ToList();
|
||||
var copy = JsonSerializer.Deserialize<List<StateKeyValue>>(JsonSerializer.Serialize(values));
|
||||
return new ConversationState(copy ?? new());
|
||||
}
|
||||
|
||||
public void SetCurrentState(ConversationState state)
|
||||
{
|
||||
var values = _curStates.Values.ToList();
|
||||
var copy = JsonSerializer.Deserialize<List<StateKeyValue>>(JsonSerializer.Serialize(values));
|
||||
_curStates = new ConversationState(copy ?? new());
|
||||
}
|
||||
|
||||
public void ResetCurrentState()
|
||||
{
|
||||
_curStates.Clear();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -69,6 +69,7 @@ public partial class LocalFileStorageService
|
|||
{
|
||||
MessageId = messageId,
|
||||
FileUrl = $"/conversation/{conversationId}/message/{messageId}/{source}/file/{index}/{fileName}",
|
||||
FileDownloadUrl = $"/conversation/{conversationId}/message/{messageId}/{source}/file/{index}/{fileName}/download",
|
||||
FileStorageUrl = file,
|
||||
FileName = fileName,
|
||||
FileExtension = fileExtension,
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ public class DistributedLocker
|
|||
}
|
||||
}
|
||||
|
||||
public async Task<T> Lock<T>(string resource, Func<T> action, int timeoutInSeconds = 30)
|
||||
public async Task Lock(string resource, Action action, int timeoutInSeconds = 30)
|
||||
{
|
||||
await ConnectToRedis();
|
||||
|
||||
|
|
@ -45,7 +45,7 @@ public class DistributedLocker
|
|||
Serilog.Log.Logger.Error($"Acquire lock for {resource} failed due to after {timeout}s timeout.");
|
||||
|
||||
}
|
||||
return action();
|
||||
action();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,70 +2,13 @@ using BotSharp.Abstraction.Loggers.Models;
|
|||
using BotSharp.Abstraction.Plugins.Models;
|
||||
using BotSharp.Abstraction.Tasks.Models;
|
||||
using BotSharp.Abstraction.Translation.Models;
|
||||
using BotSharp.Abstraction.Users.Models;
|
||||
using BotSharp.Abstraction.VectorStorage.Models;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
|
||||
namespace BotSharp.Core.Repository;
|
||||
|
||||
public class BotSharpDbContext : Database, IBotSharpRepository
|
||||
{
|
||||
public IQueryable<User> Users => throw new NotImplementedException();
|
||||
|
||||
public IQueryable<Agent> Agents => throw new NotImplementedException();
|
||||
|
||||
public IQueryable<UserAgent> UserAgents => throw new NotImplementedException();
|
||||
|
||||
public IQueryable<Conversation> Conversations => throw new NotImplementedException();
|
||||
|
||||
public int Transaction<TTableInterface>(Action action)
|
||||
{
|
||||
DatabaseFacade database = base.GetMaster(typeof(TTableInterface)).Database;
|
||||
int num = 0;
|
||||
if (database.CurrentTransaction == null)
|
||||
{
|
||||
using (Microsoft.EntityFrameworkCore.Storage.IDbContextTransaction dbContextTransaction = database.BeginTransaction())
|
||||
{
|
||||
try
|
||||
{
|
||||
action();
|
||||
num = base.SaveChanges();
|
||||
dbContextTransaction.Commit();
|
||||
return num;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
dbContextTransaction.Rollback();
|
||||
if (ex.Message.Contains("See the inner exception for details"))
|
||||
{
|
||||
throw ex.InnerException;
|
||||
}
|
||||
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
action();
|
||||
return base.SaveChanges();
|
||||
}
|
||||
catch (Exception ex2)
|
||||
{
|
||||
if (database.CurrentTransaction != null)
|
||||
{
|
||||
database.CurrentTransaction.Rollback();
|
||||
}
|
||||
|
||||
if (ex2.Message.Contains("See the inner exception for details"))
|
||||
{
|
||||
throw ex2.InnerException;
|
||||
}
|
||||
|
||||
throw ex2;
|
||||
}
|
||||
}
|
||||
public IServiceProvider ServiceProvider => throw new NotImplementedException();
|
||||
|
||||
#region Plugin
|
||||
public PluginConfig GetPluginConfig() => throw new NotImplementedException();
|
||||
|
|
@ -79,7 +22,7 @@ public class BotSharpDbContext : Database, IBotSharpRepository
|
|||
public List<Agent> GetAgents(AgentFilter filter)
|
||||
=> throw new NotImplementedException();
|
||||
|
||||
public List<Agent> GetAgentsByUser(string userId)
|
||||
public List<UserAgent> GetUserAgents(string userId)
|
||||
=> throw new NotImplementedException();
|
||||
|
||||
public void UpdateAgent(Agent agent, AgentField field)
|
||||
|
|
@ -149,12 +92,14 @@ public class BotSharpDbContext : Database, IBotSharpRepository
|
|||
public List<string> GetIdleConversations(int batchSize, int messageLimit, int bufferHours, IEnumerable<string> excludeAgentIds)
|
||||
=> throw new NotImplementedException();
|
||||
|
||||
[SideCar]
|
||||
public List<DialogElement> GetConversationDialogs(string conversationId)
|
||||
=> throw new NotImplementedException();
|
||||
|
||||
public ConversationState GetConversationStates(string conversationId)
|
||||
=> throw new NotImplementedException();
|
||||
|
||||
[SideCar]
|
||||
public void AppendConversationDialogs(string conversationId, List<DialogElement> dialogs)
|
||||
=> throw new NotImplementedException();
|
||||
|
||||
|
|
@ -167,9 +112,11 @@ public class BotSharpDbContext : Database, IBotSharpRepository
|
|||
public bool UpdateConversationMessage(string conversationId, UpdateMessageRequest request)
|
||||
=> throw new NotImplementedException();
|
||||
|
||||
[SideCar]
|
||||
public void UpdateConversationBreakpoint(string conversationId, ConversationBreakpoint breakpoint)
|
||||
=> throw new NotImplementedException();
|
||||
|
||||
[SideCar]
|
||||
public ConversationBreakpoint? GetConversationBreakpoint(string conversationId)
|
||||
=> throw new NotImplementedException();
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
using BotSharp.Abstraction.Functions.Models;
|
||||
using BotSharp.Abstraction.Agents.Models;
|
||||
using BotSharp.Abstraction.Routing.Models;
|
||||
using BotSharp.Abstraction.Users.Models;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System.IO;
|
||||
|
||||
namespace BotSharp.Core.Repository
|
||||
|
|
@ -188,7 +190,7 @@ namespace BotSharp.Core.Repository
|
|||
// Save default instructions
|
||||
var instructionFile = Path.Combine(instructionDir, $"{AGENT_INSTRUCTION_FILE}.{_agentSettings.TemplateFormat}");
|
||||
File.WriteAllText(instructionFile, instruction ?? string.Empty);
|
||||
Thread.Sleep(100);
|
||||
Thread.Sleep(50);
|
||||
|
||||
// Save channel instructions
|
||||
foreach (var ci in channelInstructions)
|
||||
|
|
@ -197,7 +199,7 @@ namespace BotSharp.Core.Repository
|
|||
|
||||
var file = Path.Combine(instructionDir, $"{AGENT_INSTRUCTION_FILE}.{ci.Channel}.{_agentSettings.TemplateFormat}");
|
||||
File.WriteAllText(file, ci.Instruction ?? string.Empty);
|
||||
Thread.Sleep(100);
|
||||
Thread.Sleep(50);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -386,19 +388,26 @@ namespace BotSharp.Core.Repository
|
|||
return query.ToList();
|
||||
}
|
||||
|
||||
public List<Agent> GetAgentsByUser(string userId)
|
||||
public List<UserAgent> GetUserAgents(string userId)
|
||||
{
|
||||
var agentIds = (from ua in UserAgents
|
||||
join u in Users on ua.UserId equals u.Id
|
||||
where ua.UserId == userId || u.ExternalId == userId
|
||||
select ua.AgentId).ToList();
|
||||
var found = (from ua in UserAgents
|
||||
join u in Users on ua.UserId equals u.Id
|
||||
where ua.UserId == userId || u.ExternalId == userId
|
||||
select ua).ToList();
|
||||
|
||||
var filter = new AgentFilter
|
||||
if (found.IsNullOrEmpty()) return [];
|
||||
|
||||
var agentIds = found.Select(x => x.AgentId).Distinct().ToList();
|
||||
var agents = GetAgents(new AgentFilter { AgentIds = agentIds });
|
||||
foreach (var item in found)
|
||||
{
|
||||
AgentIds = agentIds
|
||||
};
|
||||
var agents = GetAgents(filter);
|
||||
return agents;
|
||||
var agent = agents.FirstOrDefault(x => x.Id == item.AgentId);
|
||||
if (agent == null) continue;
|
||||
|
||||
item.Agent = agent;
|
||||
}
|
||||
|
||||
return found;
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -442,9 +451,66 @@ namespace BotSharp.Core.Repository
|
|||
return true;
|
||||
}
|
||||
|
||||
public void BulkInsertAgents(List<Agent> agents) { }
|
||||
public void BulkInsertAgents(List<Agent> agents)
|
||||
{
|
||||
if (agents.IsNullOrEmpty()) return;
|
||||
|
||||
public void BulkInsertUserAgents(List<UserAgent> userAgents) { }
|
||||
var baseDir = Path.Combine(_dbSettings.FileRepository, _agentSettings.DataDir);
|
||||
foreach (var agent in agents)
|
||||
{
|
||||
var dir = Path.Combine(baseDir, agent.Id);
|
||||
if (Directory.Exists(dir)) continue;
|
||||
|
||||
Directory.CreateDirectory(dir);
|
||||
Thread.Sleep(50);
|
||||
|
||||
var agentFile = Path.Combine(dir, AGENT_FILE);
|
||||
var json = JsonSerializer.Serialize(agent, _options);
|
||||
File.WriteAllText(agentFile, json);
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(agent.Instruction))
|
||||
{
|
||||
var instDir = Path.Combine(dir, AGENT_INSTRUCTIONS_FOLDER);
|
||||
Directory.CreateDirectory(instDir);
|
||||
var instFile = Path.Combine(instDir, $"{AGENT_INSTRUCTION_FILE}.{_agentSettings.TemplateFormat}");
|
||||
File.WriteAllText(instFile, agent.Instruction);
|
||||
}
|
||||
}
|
||||
Reset();
|
||||
}
|
||||
|
||||
public void BulkInsertUserAgents(List<UserAgent> userAgents)
|
||||
{
|
||||
if (userAgents.IsNullOrEmpty()) return;
|
||||
|
||||
var groups = userAgents.GroupBy(x => x.UserId);
|
||||
var usersDir = Path.Combine(_dbSettings.FileRepository, USERS_FOLDER);
|
||||
|
||||
foreach (var group in groups)
|
||||
{
|
||||
var filtered = group.Where(x => !string.IsNullOrEmpty(x.UserId) && !string.IsNullOrEmpty(x.AgentId)).ToList();
|
||||
if (filtered.IsNullOrEmpty()) continue;
|
||||
|
||||
filtered.ForEach(x => x.Id = Guid.NewGuid().ToString());
|
||||
var userId = filtered.First().UserId;
|
||||
var userDir = Path.Combine(usersDir, userId);
|
||||
if (!Directory.Exists(userDir)) continue;
|
||||
|
||||
var userAgentFile = Path.Combine(userDir, USER_AGENT_FILE);
|
||||
var list = new List<UserAgent>();
|
||||
if (File.Exists(userAgentFile))
|
||||
{
|
||||
var str = File.ReadAllText(userAgentFile);
|
||||
list = JsonSerializer.Deserialize<List<UserAgent>>(str, _options);
|
||||
}
|
||||
|
||||
list.AddRange(filtered);
|
||||
File.WriteAllText(userAgentFile, JsonSerializer.Serialize(list, _options));
|
||||
Thread.Sleep(50);
|
||||
}
|
||||
|
||||
Reset();
|
||||
}
|
||||
|
||||
public bool DeleteAgents()
|
||||
{
|
||||
|
|
@ -473,14 +539,14 @@ namespace BotSharp.Core.Repository
|
|||
var userAgents = JsonSerializer.Deserialize<List<UserAgent>>(text, _options);
|
||||
if (userAgents.IsNullOrEmpty()) continue;
|
||||
|
||||
userAgents = userAgents.Where(x => x.AgentId != agentId).ToList();
|
||||
userAgents = userAgents?.Where(x => x.AgentId != agentId)?.ToList() ?? [];
|
||||
File.WriteAllText(userAgentFile, JsonSerializer.Serialize(userAgents, _options));
|
||||
}
|
||||
}
|
||||
|
||||
// Delete agent folder
|
||||
Directory.Delete(agentDir, true);
|
||||
|
||||
Reset();
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
|
|
@ -488,5 +554,11 @@ namespace BotSharp.Core.Repository
|
|||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private void Reset()
|
||||
{
|
||||
_agents = [];
|
||||
_userAgents = [];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -57,6 +57,7 @@ namespace BotSharp.Core.Repository
|
|||
return true;
|
||||
}
|
||||
|
||||
[SideCar]
|
||||
public List<DialogElement> GetConversationDialogs(string conversationId)
|
||||
{
|
||||
var dialogs = new List<DialogElement>();
|
||||
|
|
@ -78,6 +79,7 @@ namespace BotSharp.Core.Repository
|
|||
return dialogs;
|
||||
}
|
||||
|
||||
[SideCar]
|
||||
public void AppendConversationDialogs(string conversationId, List<DialogElement> dialogs)
|
||||
{
|
||||
var convDir = FindConversationDirectory(conversationId);
|
||||
|
|
@ -182,6 +184,7 @@ namespace BotSharp.Core.Repository
|
|||
return true;
|
||||
}
|
||||
|
||||
[SideCar]
|
||||
public void UpdateConversationBreakpoint(string conversationId, ConversationBreakpoint breakpoint)
|
||||
{
|
||||
var convDir = FindConversationDirectory(conversationId);
|
||||
|
|
@ -220,6 +223,7 @@ namespace BotSharp.Core.Repository
|
|||
}
|
||||
}
|
||||
|
||||
[SideCar]
|
||||
public ConversationBreakpoint? GetConversationBreakpoint(string conversationId)
|
||||
{
|
||||
var convDir = FindConversationDirectory(conversationId);
|
||||
|
|
|
|||
|
|
@ -1,82 +0,0 @@
|
|||
using BotSharp.Abstraction.Agents.Models;
|
||||
using BotSharp.Abstraction.Users.Models;
|
||||
using System.IO;
|
||||
|
||||
namespace BotSharp.Core.Repository;
|
||||
|
||||
public partial class FileRepository
|
||||
{
|
||||
public void Add<TTableInterface>(object entity)
|
||||
{
|
||||
if (entity is Agent agent)
|
||||
{
|
||||
_agents.Add(agent);
|
||||
_changedTableNames.Add(nameof(Agent));
|
||||
}
|
||||
else if (entity is User user)
|
||||
{
|
||||
_users.Add(user);
|
||||
_changedTableNames.Add(nameof(User));
|
||||
}
|
||||
else if (entity is UserAgent userAgent)
|
||||
{
|
||||
_userAgents.Add(userAgent);
|
||||
_changedTableNames.Add(nameof(UserAgent));
|
||||
}
|
||||
}
|
||||
|
||||
private readonly List<string> _changedTableNames = new List<string>();
|
||||
public int Transaction<TTableInterface>(Action action)
|
||||
{
|
||||
_changedTableNames.Clear();
|
||||
action();
|
||||
|
||||
// Persist to disk
|
||||
foreach (var table in _changedTableNames)
|
||||
{
|
||||
if (table == nameof(Agent))
|
||||
{
|
||||
foreach (var agent in _agents)
|
||||
{
|
||||
var dir = Path.Combine(_dbSettings.FileRepository, _agentSettings.DataDir, agent.Id);
|
||||
if (!Directory.Exists(dir))
|
||||
{
|
||||
Directory.CreateDirectory(dir);
|
||||
}
|
||||
var path = Path.Combine(dir, AGENT_FILE);
|
||||
File.WriteAllText(path, JsonSerializer.Serialize(agent, _options));
|
||||
}
|
||||
}
|
||||
else if (table == nameof(User))
|
||||
{
|
||||
foreach (var user in _users)
|
||||
{
|
||||
var dir = Path.Combine(_dbSettings.FileRepository, USERS_FOLDER, user.Id);
|
||||
if (!Directory.Exists(dir))
|
||||
{
|
||||
Directory.CreateDirectory(dir);
|
||||
}
|
||||
var path = Path.Combine(dir, USER_FILE);
|
||||
File.WriteAllText(path, JsonSerializer.Serialize(user, _options));
|
||||
}
|
||||
}
|
||||
else if (table == nameof(UserAgent))
|
||||
{
|
||||
_userAgents.GroupBy(x => x.UserId)
|
||||
.Select(x => x.Key).ToList()
|
||||
.ForEach(uid =>
|
||||
{
|
||||
var agents = _userAgents.Where(x => x.UserId == uid).ToList();
|
||||
if (agents.Any())
|
||||
{
|
||||
var dir = Path.Combine(_dbSettings.FileRepository, USERS_FOLDER, uid);
|
||||
var path = Path.Combine(dir, USER_AGENT_FILE);
|
||||
File.WriteAllText(path, JsonSerializer.Serialize(agents, _options));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return _changedTableNames.Count;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,7 @@
|
|||
using BotSharp.Abstraction.Agents.Models;
|
||||
using BotSharp.Abstraction.Users.Enums;
|
||||
using BotSharp.Abstraction.Users.Models;
|
||||
using System;
|
||||
using System.IO;
|
||||
|
||||
namespace BotSharp.Core.Repository;
|
||||
|
|
@ -68,4 +70,100 @@ public partial class FileRepository
|
|||
var path = Path.Combine(dir, USER_FILE);
|
||||
File.WriteAllText(path, JsonSerializer.Serialize(user, _options));
|
||||
}
|
||||
|
||||
public PagedItems<User> GetUsers(UserFilter filter)
|
||||
{
|
||||
var users = Users;
|
||||
|
||||
// Apply filters
|
||||
if (!filter.UserIds.IsNullOrEmpty())
|
||||
{
|
||||
users = users.Where(x => filter.UserIds.Contains(x.Id));
|
||||
}
|
||||
if (!filter.UserNames.IsNullOrEmpty())
|
||||
{
|
||||
users = users.Where(x => filter.UserNames.Contains(x.UserName));
|
||||
}
|
||||
if (!filter.ExternalIds.IsNullOrEmpty())
|
||||
{
|
||||
users = users.Where(x => filter.ExternalIds.Contains(x.ExternalId));
|
||||
}
|
||||
if (!filter.Roles.IsNullOrEmpty())
|
||||
{
|
||||
users = users.Where(x => filter.Roles.Contains(x.Role));
|
||||
}
|
||||
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<User>
|
||||
{
|
||||
Items = users.OrderByDescending(x => x.CreatedTime).Skip(filter.Offset).Take(filter.Size),
|
||||
Count = users.Count()
|
||||
};
|
||||
}
|
||||
|
||||
public bool UpdateUser(User user, bool isUpdateUserAgents = false)
|
||||
{
|
||||
if (string.IsNullOrEmpty(user?.Id)) return false;
|
||||
|
||||
var dir = Path.Combine(_dbSettings.FileRepository, USERS_FOLDER, user.Id);
|
||||
if (!Directory.Exists(dir))
|
||||
{
|
||||
Directory.CreateDirectory(dir);
|
||||
}
|
||||
|
||||
var userFile = Path.Combine(dir, USER_FILE);
|
||||
user.UpdatedTime = DateTime.UtcNow;
|
||||
File.WriteAllText(userFile, JsonSerializer.Serialize(user, _options));
|
||||
|
||||
if (isUpdateUserAgents)
|
||||
{
|
||||
var userAgents = user.AgentActions?.Select(x => new UserAgent
|
||||
{
|
||||
Id = !string.IsNullOrEmpty(x.Id) ? x.Id : Guid.NewGuid().ToString(),
|
||||
UserId = user.Id,
|
||||
AgentId = x.AgentId,
|
||||
Actions = x.Actions ?? [],
|
||||
CreatedTime = DateTime.UtcNow,
|
||||
UpdatedTime = DateTime.UtcNow
|
||||
})?.ToList() ?? [];
|
||||
|
||||
var userAgentFile = Path.Combine(dir, USER_AGENT_FILE);
|
||||
File.WriteAllText(userAgentFile, JsonSerializer.Serialize(userAgents, _options));
|
||||
}
|
||||
|
||||
_users = [];
|
||||
_userAgents = [];
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -171,6 +171,8 @@ public partial class FileRepository : IBotSharpRepository
|
|||
}
|
||||
}
|
||||
|
||||
public IServiceProvider ServiceProvider => _services;
|
||||
|
||||
|
||||
#region Private methods
|
||||
private void DeleteBeforeCreateDirectory(string dir)
|
||||
|
|
|
|||
|
|
@ -147,7 +147,7 @@ public class SequentialPlanner : IRoutingPlaner
|
|||
context.Pop();
|
||||
|
||||
var routing = _services.GetRequiredService<IRoutingService>();
|
||||
routing.ResetRecursiveCounter();
|
||||
routing.Context.ResetRecursiveCounter();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
using BotSharp.Abstraction.Routing.Settings;
|
||||
using BotSharp.Abstraction.Utilities;
|
||||
|
||||
namespace BotSharp.Core.Routing;
|
||||
|
||||
|
|
@ -10,6 +9,7 @@ public class RoutingContext : IRoutingContext
|
|||
private string[] _routerAgentIds;
|
||||
private string _conversationId;
|
||||
private string _messageId;
|
||||
private int _currentRecursionDepth = 0;
|
||||
|
||||
public RoutingContext(IServiceProvider services, RoutingSettings setting)
|
||||
{
|
||||
|
|
@ -20,9 +20,9 @@ public class RoutingContext : IRoutingContext
|
|||
public int AgentCount => _stack.Count;
|
||||
public string ConversationId => _conversationId;
|
||||
public string MessageId => _messageId;
|
||||
public int CurrentRecursionDepth => _currentRecursionDepth;
|
||||
|
||||
private Stack<string> _stack { get; set; }
|
||||
= new Stack<string>();
|
||||
private Stack<string> _stack { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Intent name
|
||||
|
|
@ -208,4 +208,39 @@ public class RoutingContext : IRoutingContext
|
|||
_conversationId = conversationId;
|
||||
_messageId = messageId;
|
||||
}
|
||||
|
||||
public int GetRecursiveCounter()
|
||||
{
|
||||
return _currentRecursionDepth;
|
||||
}
|
||||
|
||||
public void IncreaseRecursiveCounter()
|
||||
{
|
||||
_currentRecursionDepth++;
|
||||
}
|
||||
|
||||
public void SetRecursiveCounter(int counter)
|
||||
{
|
||||
_currentRecursionDepth = counter;
|
||||
}
|
||||
|
||||
public void ResetRecursiveCounter()
|
||||
{
|
||||
_currentRecursionDepth = 0;
|
||||
}
|
||||
|
||||
public Stack<string> GetAgentStack()
|
||||
{
|
||||
return new Stack<string>(_stack);
|
||||
}
|
||||
|
||||
public void SetAgentStack(Stack<string> stack)
|
||||
{
|
||||
_stack = new Stack<string>(stack);
|
||||
}
|
||||
|
||||
public void ResetAgentStack()
|
||||
{
|
||||
_stack.Clear();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,14 +4,15 @@ namespace BotSharp.Core.Routing;
|
|||
|
||||
public partial class RoutingService
|
||||
{
|
||||
private int _currentRecursionDepth = 0;
|
||||
//private int _currentRecursionDepth = 0;
|
||||
public async Task<bool> InvokeAgent(string agentId, List<RoleDialogModel> dialogs)
|
||||
{
|
||||
var agentService = _services.GetRequiredService<IAgentService>();
|
||||
var agent = await agentService.LoadAgent(agentId);
|
||||
|
||||
_currentRecursionDepth++;
|
||||
if (_currentRecursionDepth > agent.LlmConfig.MaxRecursionDepth)
|
||||
//_currentRecursionDepth++;
|
||||
Context.IncreaseRecursiveCounter();
|
||||
if (Context.CurrentRecursionDepth > agent.LlmConfig.MaxRecursionDepth)
|
||||
{
|
||||
_logger.LogWarning($"Current recursive call depth greater than {agent.LlmConfig.MaxRecursionDepth}, which will cause unexpected result.");
|
||||
return false;
|
||||
|
|
@ -36,8 +37,7 @@ public partial class RoutingService
|
|||
|
||||
if (response.Role == AgentRole.Function)
|
||||
{
|
||||
message = RoleDialogModel.From(message,
|
||||
role: AgentRole.Function);
|
||||
message = RoleDialogModel.From(message, role: AgentRole.Function);
|
||||
if (response.FunctionName != null && response.FunctionName.Contains("/"))
|
||||
{
|
||||
response.FunctionName = response.FunctionName.Split("/").Last();
|
||||
|
|
@ -57,9 +57,7 @@ public partial class RoutingService
|
|||
response.Content = "Apologies, I'm not quite sure I understand. Could you please provide additional clarification or context?";
|
||||
}
|
||||
|
||||
message = RoleDialogModel.From(message,
|
||||
role: AgentRole.Assistant,
|
||||
content: response.Content);
|
||||
message = RoleDialogModel.From(message, role: AgentRole.Assistant, content: response.Content);
|
||||
message.CurrentAgentId = agent.Id;
|
||||
dialogs.Add(message);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,12 +16,23 @@ public partial class RoutingService : IRoutingService
|
|||
public IRoutingContext Context => _context;
|
||||
public Agent Router => _router;
|
||||
|
||||
public void ResetRecursiveCounter()
|
||||
{
|
||||
_currentRecursionDepth = 0;
|
||||
}
|
||||
//public int GetRecursiveCounter()
|
||||
//{
|
||||
// return _currentRecursionDepth;
|
||||
//}
|
||||
|
||||
public RoutingService(IServiceProvider services,
|
||||
//public void SetRecursiveCounter(int counter)
|
||||
//{
|
||||
// _currentRecursionDepth = counter;
|
||||
//}
|
||||
|
||||
//public void ResetRecursiveCounter()
|
||||
//{
|
||||
// _currentRecursionDepth = 0;
|
||||
//}
|
||||
|
||||
public RoutingService(
|
||||
IServiceProvider services,
|
||||
RoutingSettings settings,
|
||||
IRoutingContext context,
|
||||
ILogger<RoutingService> logger)
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ public class TaskPlugin : IBotSharpPlugin
|
|||
var section = menu.First(x => x.Label == "Apps");
|
||||
menu.Add(new PluginMenuDef("Task", link: "page/task", icon: "bx bx-task", weight: section.Weight + 8)
|
||||
{
|
||||
Roles = new List<string> { UserRole.Admin }
|
||||
Roles = new List<string> { UserRole.Root, UserRole.Admin }
|
||||
});
|
||||
|
||||
return true;
|
||||
|
|
|
|||
|
|
@ -70,18 +70,15 @@ public class UserIdentity : IUserIdentity
|
|||
[JsonPropertyName("phone")]
|
||||
public string? Phone => _claims?.FirstOrDefault(x => x.Type == "phone")?.Value;
|
||||
|
||||
[JsonPropertyName("affiliateId")]
|
||||
public string? AffiliateId => _claims?.FirstOrDefault(x => x.Type == "affiliateId")?.Value;
|
||||
|
||||
[JsonPropertyName("employeeId")]
|
||||
public string? EmployeeId => _claims?.FirstOrDefault(x => x.Type == "employeeId")?.Value;
|
||||
|
||||
[JsonPropertyName("type")]
|
||||
public string? Type => _claims?.FirstOrDefault(x => x.Type == "type")?.Value;
|
||||
|
||||
[JsonPropertyName("role")]
|
||||
public string? Role => _claims?.FirstOrDefault(x => x.Type == ClaimTypes.Role)?.Value;
|
||||
|
||||
[JsonPropertyName("regionCode")]
|
||||
public string? RegionCode => _claims?.FirstOrDefault(x => x.Type == "regionCode")?.Value;
|
||||
/// <summary>
|
||||
/// US, CA, etc.
|
||||
/// </summary>
|
||||
[JsonPropertyName("region_code")]
|
||||
public string? RegionCode => _claims?.FirstOrDefault(x => x.Type == "region_code")?.Value;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -400,6 +400,19 @@ public class UserService : IUserService
|
|||
return user;
|
||||
}
|
||||
|
||||
public async Task<PagedItems<User>> GetUsers(UserFilter filter)
|
||||
{
|
||||
var db = _services.GetRequiredService<IBotSharpRepository>();
|
||||
var users = db.GetUsers(filter);
|
||||
return users;
|
||||
}
|
||||
|
||||
public async Task<bool> UpdateUser(User model, bool isUpdateUserAgents = false)
|
||||
{
|
||||
var db = _services.GetRequiredService<IBotSharpRepository>();
|
||||
return db.UpdateUser(model, isUpdateUserAgents);
|
||||
}
|
||||
|
||||
public async Task<Token> ActiveUser(UserActivationModel model)
|
||||
{
|
||||
var id = model.UserName;
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@ global using BotSharp.Abstraction.Files.Utilities;
|
|||
global using BotSharp.Abstraction.Translation.Attributes;
|
||||
global using BotSharp.Abstraction.Messaging.Enums;
|
||||
global using BotSharp.Abstraction.Knowledges.Models;
|
||||
global using BotSharp.Abstraction.SideCar.Attributes;
|
||||
global using BotSharp.Core.Repository;
|
||||
global using BotSharp.Core.Routing;
|
||||
global using BotSharp.Core.Agents.Services;
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
using BotSharp.Abstraction.Agents.Models;
|
||||
using BotSharp.Abstraction.Users.Enums;
|
||||
|
||||
namespace BotSharp.OpenAPI.Controllers;
|
||||
|
|
@ -58,24 +59,64 @@ public class AgentController : ControllerBase
|
|||
}
|
||||
|
||||
var editable = true;
|
||||
var chatable = true;
|
||||
var userService = _services.GetRequiredService<IUserService>();
|
||||
var user = await userService.GetUser(_user.Id);
|
||||
if (user?.Role != UserRole.Admin)
|
||||
if (!UserConstant.AdminRoles.Contains(user?.Role))
|
||||
{
|
||||
var userAgents = _agentService.GetAgentsByUser(user?.Id);
|
||||
editable = userAgents?.Select(x => x.Id)?.Contains(targetAgent.Id) ?? false;
|
||||
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);
|
||||
}
|
||||
|
||||
targetAgent.Editable = editable;
|
||||
targetAgent.Chatable = chatable;
|
||||
return targetAgent;
|
||||
}
|
||||
|
||||
[HttpGet("/agents")]
|
||||
public async Task<PagedItems<AgentViewModel>> GetAgents([FromQuery] AgentFilter filter)
|
||||
public async Task<PagedItems<AgentViewModel>> GetAgents([FromQuery] AgentFilter filter, [FromQuery] bool checkAuth = false)
|
||||
{
|
||||
var agentSetting = _services.GetRequiredService<AgentSettings>();
|
||||
var userService = _services.GetRequiredService<IUserService>();
|
||||
|
||||
List<AgentViewModel> agents;
|
||||
var pagedAgents = await _agentService.GetAgents(filter);
|
||||
var agents = pagedAgents?.Items?.Select(x => AgentViewModel.FromAgent(x))?.ToList() ?? new List<AgentViewModel>();
|
||||
|
||||
if (!checkAuth)
|
||||
{
|
||||
agents = pagedAgents?.Items?.Select(x => AgentViewModel.FromAgent(x))?.ToList() ?? [];
|
||||
return new PagedItems<AgentViewModel>
|
||||
{
|
||||
Items = agents,
|
||||
Count = pagedAgents?.Count ?? 0
|
||||
};
|
||||
}
|
||||
|
||||
var userAgents = new List<UserAgent>();
|
||||
var user = await userService.GetUser(_user.Id);
|
||||
if (!UserConstant.AdminRoles.Contains(user.Role))
|
||||
{
|
||||
userAgents = await _agentService.GetUserAgents(user.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;
|
||||
return model;
|
||||
})?.ToList() ?? [];
|
||||
|
||||
return new PagedItems<AgentViewModel>
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
using Azure;
|
||||
using BotSharp.Abstraction.Files.Constants;
|
||||
using BotSharp.Abstraction.Files.Enums;
|
||||
using BotSharp.Abstraction.Files.Utilities;
|
||||
using BotSharp.Abstraction.Options;
|
||||
using BotSharp.Abstraction.Routing;
|
||||
using BotSharp.Abstraction.Users.Enums;
|
||||
|
|
@ -55,7 +56,7 @@ public class ConversationController : ControllerBase
|
|||
return new PagedItems<ConversationViewModel>();
|
||||
}
|
||||
|
||||
filter.UserId = user.Role != UserRole.Admin ? user.Id : filter.UserId;
|
||||
filter.UserId = !UserConstant.AdminRoles.Contains(user?.Role) ? user.Id : filter.UserId;
|
||||
var conversations = await convService.GetConversations(filter);
|
||||
var agentService = _services.GetRequiredService<IAgentService>();
|
||||
var list = conversations.Items.Select(x => ConversationViewModel.FromSession(x)).ToList();
|
||||
|
|
@ -146,7 +147,7 @@ public class ConversationController : ControllerBase
|
|||
var filter = new ConversationFilter
|
||||
{
|
||||
Id = conversationId,
|
||||
UserId = user.Role != UserRole.Admin ? user.Id : null
|
||||
UserId = !UserConstant.AdminRoles.Contains(user?.Role) ? user.Id : null
|
||||
};
|
||||
var conversations = await service.GetConversations(filter);
|
||||
if (conversations.Items.IsNullOrEmpty())
|
||||
|
|
@ -209,7 +210,7 @@ public class ConversationController : ControllerBase
|
|||
var filter = new ConversationFilter
|
||||
{
|
||||
Id = conversationId,
|
||||
UserId = user.Role != UserRole.Admin ? user.Id : null
|
||||
UserId = !UserConstant.AdminRoles.Contains(user?.Role) ? user.Id : null
|
||||
};
|
||||
var conversations = await conv.GetConversations(filter);
|
||||
|
||||
|
|
@ -262,7 +263,7 @@ public class ConversationController : ControllerBase
|
|||
var filter = new ConversationFilter
|
||||
{
|
||||
Id = conversationId,
|
||||
UserId = user.Role != UserRole.Admin ? user.Id : null
|
||||
UserId = !UserConstant.AdminRoles.Contains(user?.Role) ? user.Id : null
|
||||
};
|
||||
var conversations = await conversationService.GetConversations(filter);
|
||||
|
||||
|
|
@ -488,6 +489,26 @@ public class ConversationController : ControllerBase
|
|||
}
|
||||
return BuildFileResult(file);
|
||||
}
|
||||
|
||||
[HttpGet("/conversation/{conversationId}/message/{messageId}/{source}/file/{index}/{fileName}/download")]
|
||||
public IActionResult DownloadMessageFile([FromRoute] string conversationId, [FromRoute] string messageId, [FromRoute] string source, [FromRoute] string index, [FromRoute] string fileName)
|
||||
{
|
||||
var fileStorage = _services.GetRequiredService<IFileStorageService>();
|
||||
var file = fileStorage.GetMessageFile(conversationId, messageId, source, index, fileName);
|
||||
if (string.IsNullOrEmpty(file))
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
var fName = file.Split(Path.DirectorySeparatorChar).Last();
|
||||
var contentType = FileUtility.GetFileContentType(fName);
|
||||
var stream = System.IO.File.Open(file, FileMode.Open, FileAccess.Read, FileShare.Read);
|
||||
var bytes = new byte[stream.Length];
|
||||
stream.Read(bytes, 0, (int)stream.Length);
|
||||
stream.Position = 0;
|
||||
|
||||
return new FileStreamResult(stream, contentType) { FileDownloadName = fName };
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Private methods
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ public class PluginController : ControllerBase
|
|||
{
|
||||
var userService = _services.GetRequiredService<IUserService>();
|
||||
var user = await userService.GetUser(_user.Id);
|
||||
if (user?.Role != UserRole.Admin)
|
||||
if (!UserConstant.AdminRoles.Contains(user?.Role))
|
||||
{
|
||||
return new PagedItems<PluginDef>();
|
||||
}
|
||||
|
|
@ -45,15 +45,19 @@ public class PluginController : ControllerBase
|
|||
new PluginMenuDef("System", weight: 30)
|
||||
{
|
||||
IsHeader = true,
|
||||
Roles = new List<string> { UserRole.Admin }
|
||||
Roles = new List<string> { UserRole.Root, UserRole.Admin }
|
||||
},
|
||||
new PluginMenuDef("Plugins", link: "page/plugin", icon: "bx bx-plug", weight: 31)
|
||||
{
|
||||
Roles = new List<string> { UserRole.Admin }
|
||||
Roles = new List<string> { UserRole.Root, UserRole.Admin }
|
||||
},
|
||||
new PluginMenuDef("Settings", link: "page/setting", icon: "bx bx-cog", weight: 32)
|
||||
{
|
||||
Roles = new List<string> { UserRole.Admin }
|
||||
Roles = new List<string> { UserRole.Root, UserRole.Admin }
|
||||
},
|
||||
new PluginMenuDef("Users", link: "page/users", icon: "bx bx-user", weight: 33)
|
||||
{
|
||||
Roles = new List<string> { UserRole.Root, UserRole.Admin }
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
using BotSharp.Abstraction.Users.Settings;
|
||||
using BotSharp.Abstraction.Users.Enums;
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
using Microsoft.AspNetCore.Authentication.Cookies;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
|
@ -11,11 +12,18 @@ public class UserController : ControllerBase
|
|||
{
|
||||
private readonly IServiceProvider _services;
|
||||
private readonly IUserService _userService;
|
||||
private readonly IUserIdentity _user;
|
||||
private readonly AccountSetting _setting;
|
||||
public UserController(IUserService userService, IServiceProvider services, AccountSetting setting)
|
||||
|
||||
public UserController(
|
||||
IUserService userService,
|
||||
IServiceProvider services,
|
||||
IUserIdentity user,
|
||||
AccountSetting setting)
|
||||
{
|
||||
_services = services;
|
||||
_userService = userService;
|
||||
_user = user;
|
||||
_setting = setting;
|
||||
}
|
||||
|
||||
|
|
@ -169,6 +177,46 @@ public class UserController : ControllerBase
|
|||
return await _userService.UpdateUsersIsDisable(userIds, isDisable);
|
||||
}
|
||||
|
||||
#region User management
|
||||
[HttpPost("/users")]
|
||||
public async Task<PagedItems<UserViewModel>> GetUsers([FromBody] UserFilter filter)
|
||||
{
|
||||
var userService = _services.GetRequiredService<IUserService>();
|
||||
var user = await userService.GetUser(_user.Id);
|
||||
if (user == null || !UserConstant.AdminRoles.Contains(user.Role))
|
||||
{
|
||||
return new PagedItems<UserViewModel>();
|
||||
}
|
||||
|
||||
var users = await userService.GetUsers(filter);
|
||||
var views = users.Items.Select(x => UserViewModel.FromUser(x)).ToList();
|
||||
|
||||
return new PagedItems<UserViewModel>
|
||||
{
|
||||
Count = users.Count,
|
||||
Items = views
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
[HttpPut("/user")]
|
||||
public async Task<bool> UpdateUser([FromBody] UserUpdateModel model)
|
||||
{
|
||||
if (model == null) return false;
|
||||
|
||||
var userService = _services.GetRequiredService<IUserService>();
|
||||
var user = await userService.GetUser(_user.Id);
|
||||
if (user == null || !UserConstant.AdminRoles.Contains(user.Role))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var updated = await userService.UpdateUser(UserUpdateModel.ToUser(model), isUpdateUserAgents: true);
|
||||
return updated;
|
||||
}
|
||||
#endregion
|
||||
|
||||
|
||||
#region Avatar
|
||||
[HttpPost("/user/avatar")]
|
||||
public bool UploadUserAvatar([FromBody] UserAvatarModel input)
|
||||
|
|
|
|||
|
|
@ -47,6 +47,7 @@ public class AgentViewModel
|
|||
public PluginDef Plugin { get; set; }
|
||||
|
||||
public bool Editable { get; set; }
|
||||
public bool Chatable { get; set; }
|
||||
|
||||
[JsonPropertyName("created_datetime")]
|
||||
public DateTime CreatedDateTime { get; set; }
|
||||
|
|
|
|||
|
|
@ -19,6 +19,10 @@ public class MessageFileViewModel
|
|||
[JsonPropertyName("file_source")]
|
||||
public string FileSource { get; set; }
|
||||
|
||||
[JsonPropertyName("file_download_url")]
|
||||
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
||||
public string? FileDownloadUrl { get; set; }
|
||||
|
||||
public MessageFileViewModel()
|
||||
{
|
||||
|
||||
|
|
@ -32,7 +36,8 @@ public class MessageFileViewModel
|
|||
FileName = model.FileName,
|
||||
FileExtension = model.FileExtension,
|
||||
ContentType = model.ContentType,
|
||||
FileSource = model.FileSource
|
||||
FileSource = model.FileSource,
|
||||
FileDownloadUrl = model.FileDownloadUrl
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,40 @@
|
|||
using BotSharp.Abstraction.Agents.Models;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace BotSharp.OpenAPI.ViewModels.Users;
|
||||
|
||||
public class UserAgentActionViewModel
|
||||
{
|
||||
[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<string> Actions { get; set; } = [];
|
||||
|
||||
public static UserAgentActionViewModel ToViewModel(UserAgentAction action)
|
||||
{
|
||||
return new UserAgentActionViewModel
|
||||
{
|
||||
Id = action.Id,
|
||||
AgentId = action.AgentId,
|
||||
Agent = action.Agent,
|
||||
Actions = action.Actions
|
||||
};
|
||||
}
|
||||
|
||||
public static UserAgentAction ToDomainModel(UserAgentActionViewModel action)
|
||||
{
|
||||
return new UserAgentAction
|
||||
{
|
||||
Id = action.Id,
|
||||
AgentId = action.AgentId,
|
||||
Actions = action.Actions
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace BotSharp.OpenAPI.ViewModels.Users;
|
||||
|
||||
public class UserUpdateModel
|
||||
{
|
||||
public string Id { get; set; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("user_name")]
|
||||
public string UserName { get; set; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("first_name")]
|
||||
public string FirstName { get; set; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("last_name")]
|
||||
public string? LastName { get; set; }
|
||||
public string? Email { get; set; }
|
||||
public string? Phone { get; set; }
|
||||
public string? Type { get; set; }
|
||||
public string? Role { get; set; }
|
||||
public string? Source { get; set; }
|
||||
|
||||
[JsonPropertyName("external_id")]
|
||||
public string? ExternalId { get; set; }
|
||||
|
||||
public IEnumerable<string> Permissions { get; set; } = [];
|
||||
|
||||
[JsonPropertyName("agent_actions")]
|
||||
public IEnumerable<UserAgentActionViewModel> AgentActions { get; set; } = [];
|
||||
|
||||
public static User ToUser(UserUpdateModel model)
|
||||
{
|
||||
return new User
|
||||
{
|
||||
Id = model.Id,
|
||||
UserName = model.UserName,
|
||||
FirstName = model.FirstName,
|
||||
LastName = model.LastName,
|
||||
Email = model.Email,
|
||||
Phone = model.Phone,
|
||||
Type = model.Type,
|
||||
Role = model.Role,
|
||||
Source = model.Source,
|
||||
ExternalId = model.ExternalId,
|
||||
Permissions = model.Permissions,
|
||||
AgentActions = model.AgentActions?.Select(x => UserAgentActionViewModel.ToDomainModel(x)) ?? []
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -16,14 +16,23 @@ public class UserViewModel
|
|||
public string? Phone { get; set; }
|
||||
public string Type { get; set; } = UserType.Client;
|
||||
public string Role { get; set; } = UserRole.User;
|
||||
|
||||
[JsonPropertyName("full_name")]
|
||||
public string FullName => $"{FirstName} {LastName}".Trim();
|
||||
public string? Source { get; set; }
|
||||
|
||||
[JsonPropertyName("external_id")]
|
||||
public string? ExternalId { get; set; }
|
||||
public string Avatar { get; set; } = "/user/avatar";
|
||||
|
||||
public IEnumerable<string> Permissions { get; set; } = [];
|
||||
|
||||
[JsonPropertyName("agent_actions")]
|
||||
public IEnumerable<UserAgentActionViewModel> AgentActions { get; set; } = [];
|
||||
|
||||
[JsonPropertyName("create_date")]
|
||||
public DateTime CreateDate { get; set; }
|
||||
|
||||
[JsonPropertyName("update_date")]
|
||||
public DateTime UpdateDate { get; set; }
|
||||
|
||||
|
|
@ -54,6 +63,8 @@ public class UserViewModel
|
|||
Role = user.Role,
|
||||
Source = user.Source,
|
||||
ExternalId = user.ExternalId,
|
||||
Permissions = user.Permissions,
|
||||
AgentActions = user.AgentActions?.Select(x => UserAgentActionViewModel.ToViewModel(x)) ?? [],
|
||||
CreateDate = user.CreatedTime,
|
||||
UpdateDate = user.UpdatedTime,
|
||||
Avatar = "/user/avatar",
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
using BotSharp.Abstraction.SideCar;
|
||||
using Microsoft.AspNetCore.SignalR;
|
||||
|
||||
namespace BotSharp.Plugin.ChatHub.Hooks;
|
||||
|
|
@ -32,6 +33,8 @@ public class ChatHubConversationHook : ConversationHookBase
|
|||
|
||||
public override async Task OnConversationInitialized(Conversation conversation)
|
||||
{
|
||||
if (!AllowSendingMessage()) return;
|
||||
|
||||
var userService = _services.GetRequiredService<IUserService>();
|
||||
var conv = ConversationViewModel.FromSession(conversation);
|
||||
|
||||
|
|
@ -44,6 +47,8 @@ public class ChatHubConversationHook : ConversationHookBase
|
|||
|
||||
public override async Task OnMessageReceived(RoleDialogModel message)
|
||||
{
|
||||
if (!AllowSendingMessage()) return;
|
||||
|
||||
var conv = _services.GetRequiredService<IConversationService>();
|
||||
var userService = _services.GetRequiredService<IUserService>();
|
||||
var sender = await userService.GetMyProfile();
|
||||
|
|
@ -90,6 +95,8 @@ public class ChatHubConversationHook : ConversationHookBase
|
|||
|
||||
public override async Task OnResponseGenerated(RoleDialogModel message)
|
||||
{
|
||||
if (!AllowSendingMessage()) return;
|
||||
|
||||
var conv = _services.GetRequiredService<IConversationService>();
|
||||
var json = JsonSerializer.Serialize(new ChatResponseModel()
|
||||
{
|
||||
|
|
@ -156,6 +163,12 @@ public class ChatHubConversationHook : ConversationHookBase
|
|||
}
|
||||
|
||||
#region Private methods
|
||||
private bool AllowSendingMessage()
|
||||
{
|
||||
var sidecar = _services.GetService<IConversationSideCar>();
|
||||
return sidecar == null || !sidecar.IsEnabled();
|
||||
}
|
||||
|
||||
private async Task InitClientConversation(ConversationViewModel conversation)
|
||||
{
|
||||
await _chatHub.Clients.User(_user.Id).SendAsync(INIT_CLIENT_CONVERSATION, conversation);
|
||||
|
|
|
|||
|
|
@ -37,6 +37,7 @@ public class WebSocketsMiddleware
|
|||
var regexes = new List<Regex>
|
||||
{
|
||||
new Regex(@"/conversation/(.*?)/message/(.*?)/(.*?)/file/(.*?)/(.*?)", RegexOptions.IgnoreCase),
|
||||
new Regex(@"/conversation/(.*?)/message/(.*?)/(.*?)/file/(.*?)/(.*?)/download", RegexOptions.IgnoreCase),
|
||||
new Regex(@"/user/avatar", RegexOptions.IgnoreCase),
|
||||
new Regex(@"/knowledge/document/(.*?)/file/(.*?)", RegexOptions.IgnoreCase)
|
||||
};
|
||||
|
|
|
|||
|
|
@ -4,8 +4,7 @@ public class UserAgentDocument : MongoBase
|
|||
{
|
||||
public string UserId { get; set; }
|
||||
public string AgentId { get; set; }
|
||||
public bool Editable { get; set; }
|
||||
|
||||
public IEnumerable<string> Actions { get; set; } = [];
|
||||
public DateTime CreatedTime { get; set; }
|
||||
public DateTime UpdatedTime { get; set; }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ public class UserDocument : MongoBase
|
|||
public string? AffiliateId { get; set; }
|
||||
public string? EmployeeId { get; set; }
|
||||
public bool IsDisabled { get; set; }
|
||||
public IEnumerable<string> Permissions { get; set; } = [];
|
||||
public DateTime CreatedTime { get; set; }
|
||||
public DateTime UpdatedTime { get; set; }
|
||||
|
||||
|
|
@ -47,6 +48,7 @@ public class UserDocument : MongoBase
|
|||
VerificationCode = VerificationCode,
|
||||
Verified = Verified,
|
||||
RegionCode = RegionCode,
|
||||
Permissions = Permissions,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -37,7 +37,7 @@ public class MongoStoragePlugin : IBotSharpPlugin
|
|||
var section = menu.First(x => x.Label == "Apps");
|
||||
menu.Add(new PluginMenuDef("MongoDB", icon: "bx bx-data", link: "page/mongodb", weight: section.Weight + 10)
|
||||
{
|
||||
Roles = new List<string> { UserRole.Admin }
|
||||
Roles = new List<string> { UserRole.Root, UserRole.Admin }
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -318,19 +318,36 @@ public partial class MongoRepository
|
|||
return agentDocs.Select(x => TransformAgentDocument(x)).ToList();
|
||||
}
|
||||
|
||||
public List<Agent> GetAgentsByUser(string userId)
|
||||
public List<UserAgent> GetUserAgents(string userId)
|
||||
{
|
||||
var agentIds = (from ua in _dc.UserAgents.AsQueryable()
|
||||
join u in _dc.Users.AsQueryable() on ua.UserId equals u.Id
|
||||
where ua.UserId == userId || u.ExternalId == userId
|
||||
select ua.AgentId).ToList();
|
||||
var found = (from ua in _dc.UserAgents.AsQueryable()
|
||||
join u in _dc.Users.AsQueryable() on ua.UserId equals u.Id
|
||||
where ua.UserId == userId || u.ExternalId == userId
|
||||
select ua).ToList();
|
||||
|
||||
var filter = new AgentFilter
|
||||
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
|
||||
{
|
||||
AgentIds = agentIds
|
||||
};
|
||||
var agents = GetAgents(filter);
|
||||
return agents;
|
||||
Id = x.Id,
|
||||
UserId = x.UserId,
|
||||
AgentId = x.AgentId,
|
||||
Actions = x.Actions,
|
||||
CreatedTime = x.CreatedTime,
|
||||
UpdatedTime = x.UpdatedTime
|
||||
}).ToList();
|
||||
|
||||
foreach (var item in res)
|
||||
{
|
||||
var agent = agents.FirstOrDefault(x => x.Id == item.AgentId);
|
||||
if (agent == null) continue;
|
||||
|
||||
item.Agent = agent;
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
public List<string> GetAgentResponses(string agentId, string prefix, string intent)
|
||||
|
|
@ -412,12 +429,15 @@ public partial class MongoRepository
|
|||
{
|
||||
if (userAgents.IsNullOrEmpty()) return;
|
||||
|
||||
var userAgentDocs = userAgents.Select(x => new UserAgentDocument
|
||||
var filtered = userAgents.Where(x => !string.IsNullOrEmpty(x.UserId) && !string.IsNullOrEmpty(x.AgentId)).ToList();
|
||||
if (filtered.IsNullOrEmpty()) return;
|
||||
|
||||
var userAgentDocs = filtered.Select(x => new UserAgentDocument
|
||||
{
|
||||
Id = !string.IsNullOrEmpty(x.Id) ? x.Id : Guid.NewGuid().ToString(),
|
||||
UserId = x.UserId,
|
||||
AgentId = x.AgentId,
|
||||
UserId = !string.IsNullOrEmpty(x.UserId) ? x.UserId : string.Empty,
|
||||
Editable = x.Editable,
|
||||
Actions = x.Actions,
|
||||
CreatedTime = x.CreatedTime,
|
||||
UpdatedTime = x.UpdatedTime
|
||||
}).ToList();
|
||||
|
|
@ -446,11 +466,11 @@ public partial class MongoRepository
|
|||
if (string.IsNullOrEmpty(agentId)) return false;
|
||||
|
||||
var agentFilter = Builders<AgentDocument>.Filter.Eq(x => x.Id, agentId);
|
||||
var agentUserFilter = Builders<UserAgentDocument>.Filter.Eq(x => x.AgentId, agentId);
|
||||
var userAgentFilter = Builders<UserAgentDocument>.Filter.Eq(x => x.AgentId, agentId);
|
||||
var agentTaskFilter = Builders<AgentTaskDocument>.Filter.Eq(x => x.AgentId, agentId);
|
||||
|
||||
_dc.Agents.DeleteOne(agentFilter);
|
||||
_dc.UserAgents.DeleteMany(agentUserFilter);
|
||||
_dc.UserAgents.DeleteMany(userAgentFilter);
|
||||
_dc.AgentTasks.DeleteMany(agentTaskFilter);
|
||||
return true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -70,6 +70,7 @@ public partial class MongoRepository
|
|||
|| contentLogDeleted.DeletedCount > 0 || stateLogDeleted.DeletedCount > 0;
|
||||
}
|
||||
|
||||
[SideCar]
|
||||
public List<DialogElement> GetConversationDialogs(string conversationId)
|
||||
{
|
||||
var dialogs = new List<DialogElement>();
|
||||
|
|
@ -83,6 +84,7 @@ public partial class MongoRepository
|
|||
return formattedDialog ?? new List<DialogElement>();
|
||||
}
|
||||
|
||||
[SideCar]
|
||||
public void AppendConversationDialogs(string conversationId, List<DialogElement> dialogs)
|
||||
{
|
||||
if (string.IsNullOrEmpty(conversationId)) return;
|
||||
|
|
@ -159,6 +161,7 @@ public partial class MongoRepository
|
|||
return true;
|
||||
}
|
||||
|
||||
[SideCar]
|
||||
public void UpdateConversationBreakpoint(string conversationId, ConversationBreakpoint breakpoint)
|
||||
{
|
||||
if (string.IsNullOrEmpty(conversationId)) return;
|
||||
|
|
@ -176,6 +179,7 @@ public partial class MongoRepository
|
|||
_dc.ConversationStates.UpdateOne(filterState, updateState);
|
||||
}
|
||||
|
||||
[SideCar]
|
||||
public ConversationBreakpoint? GetConversationBreakpoint(string conversationId)
|
||||
{
|
||||
if (string.IsNullOrEmpty(conversationId))
|
||||
|
|
|
|||
|
|
@ -1,157 +0,0 @@
|
|||
using BotSharp.Abstraction.Agents.Models;
|
||||
using BotSharp.Abstraction.Users.Models;
|
||||
|
||||
namespace BotSharp.Plugin.MongoStorage.Repository;
|
||||
|
||||
public partial class MongoRepository
|
||||
{
|
||||
public void Add<TTableInterface>(object entity)
|
||||
{
|
||||
if (entity is Agent agent)
|
||||
{
|
||||
_agents.Add(agent);
|
||||
_changedTableNames.Add(nameof(Agent));
|
||||
}
|
||||
else if (entity is User user)
|
||||
{
|
||||
_users.Add(user);
|
||||
_changedTableNames.Add(nameof(User));
|
||||
}
|
||||
else if (entity is UserAgent userAgent)
|
||||
{
|
||||
_userAgents.Add(userAgent);
|
||||
_changedTableNames.Add(nameof(UserAgent));
|
||||
}
|
||||
}
|
||||
|
||||
public int Transaction<TTableInterface>(Action action)
|
||||
{
|
||||
_changedTableNames.Clear();
|
||||
action();
|
||||
|
||||
foreach (var table in _changedTableNames)
|
||||
{
|
||||
if (table == nameof(Agent))
|
||||
{
|
||||
var agents = _agents.Select(x => new AgentDocument
|
||||
{
|
||||
Id = !string.IsNullOrEmpty(x.Id) ? x.Id : Guid.NewGuid().ToString(),
|
||||
Name = x.Name,
|
||||
IconUrl = x.IconUrl,
|
||||
Description = x.Description,
|
||||
Instruction = x.Instruction,
|
||||
ChannelInstructions = x.ChannelInstructions?
|
||||
.Select(i => ChannelInstructionMongoElement.ToMongoElement(i))?
|
||||
.ToList() ?? new List<ChannelInstructionMongoElement>(),
|
||||
Templates = x.Templates?
|
||||
.Select(t => AgentTemplateMongoElement.ToMongoElement(t))?
|
||||
.ToList() ?? new List<AgentTemplateMongoElement>(),
|
||||
Functions = x.Functions?
|
||||
.Select(f => FunctionDefMongoElement.ToMongoElement(f))?
|
||||
.ToList() ?? new List<FunctionDefMongoElement>(),
|
||||
Responses = x.Responses?
|
||||
.Select(r => AgentResponseMongoElement.ToMongoElement(r))?
|
||||
.ToList() ?? new List<AgentResponseMongoElement>(),
|
||||
Samples = x.Samples ?? new List<string>(),
|
||||
Utilities = x.Utilities ?? new List<string>(),
|
||||
IsPublic = x.IsPublic,
|
||||
Type = x.Type,
|
||||
InheritAgentId = x.InheritAgentId,
|
||||
Disabled = x.Disabled,
|
||||
Profiles = x.Profiles,
|
||||
RoutingRules = x.RoutingRules?
|
||||
.Select(r => RoutingRuleMongoElement.ToMongoElement(r))?
|
||||
.ToList() ?? new List<RoutingRuleMongoElement>(),
|
||||
LlmConfig = AgentLlmConfigMongoElement.ToMongoElement(x.LlmConfig),
|
||||
CreatedTime = x.CreatedDateTime,
|
||||
UpdatedTime = x.UpdatedDateTime
|
||||
}).ToList();
|
||||
|
||||
foreach (var agent in agents)
|
||||
{
|
||||
var filter = Builders<AgentDocument>.Filter.Eq(x => x.Id, agent.Id);
|
||||
var update = Builders<AgentDocument>.Update
|
||||
.Set(x => x.Name, agent.Name)
|
||||
.Set(x => x.Description, agent.Description)
|
||||
.Set(x => x.Instruction, agent.Instruction)
|
||||
.Set(x => x.ChannelInstructions, agent.ChannelInstructions)
|
||||
.Set(x => x.Templates, agent.Templates)
|
||||
.Set(x => x.Functions, agent.Functions)
|
||||
.Set(x => x.Responses, agent.Responses)
|
||||
.Set(x => x.Samples, agent.Samples)
|
||||
.Set(x => x.Utilities, agent.Utilities)
|
||||
.Set(x => x.IsPublic, agent.IsPublic)
|
||||
.Set(x => x.Type, agent.Type)
|
||||
.Set(x => x.InheritAgentId, agent.InheritAgentId)
|
||||
.Set(x => x.Disabled, agent.Disabled)
|
||||
.Set(x => x.Profiles, agent.Profiles)
|
||||
.Set(x => x.RoutingRules, agent.RoutingRules)
|
||||
.Set(x => x.LlmConfig, agent.LlmConfig)
|
||||
.Set(x => x.CreatedTime, agent.CreatedTime)
|
||||
.Set(x => x.UpdatedTime, agent.UpdatedTime);
|
||||
_dc.Agents.UpdateOne(filter, update, _options);
|
||||
}
|
||||
}
|
||||
else if (table == nameof(User))
|
||||
{
|
||||
var users = _users.Select(x => new UserDocument
|
||||
{
|
||||
Id = !string.IsNullOrEmpty(x.Id) ? x.Id : Guid.NewGuid().ToString(),
|
||||
UserName = x.UserName,
|
||||
FirstName = x.FirstName,
|
||||
LastName = x.LastName,
|
||||
Salt = x.Salt,
|
||||
Password = x.Password,
|
||||
Email = x.Email,
|
||||
ExternalId = x.ExternalId,
|
||||
Role = x.Role,
|
||||
CreatedTime = x.CreatedTime,
|
||||
UpdatedTime = x.UpdatedTime
|
||||
}).ToList();
|
||||
|
||||
foreach (var user in users)
|
||||
{
|
||||
var filter = Builders<UserDocument>.Filter.Eq(x => x.Id, user.Id);
|
||||
var update = Builders<UserDocument>.Update
|
||||
.Set(x => x.UserName, user.UserName)
|
||||
.Set(x => x.FirstName, user.FirstName)
|
||||
.Set(x => x.LastName, user.LastName)
|
||||
.Set(x => x.Email, user.Email)
|
||||
.Set(x => x.Salt, user.Salt)
|
||||
.Set(x => x.Password, user.Password)
|
||||
.Set(x => x.ExternalId, user.ExternalId)
|
||||
.Set(x => x.Role, user.Role)
|
||||
.Set(x => x.CreatedTime, user.CreatedTime)
|
||||
.Set(x => x.UpdatedTime, user.UpdatedTime);
|
||||
_dc.Users.UpdateOne(filter, update, _options);
|
||||
}
|
||||
}
|
||||
else if (table == nameof(UserAgent))
|
||||
{
|
||||
var userAgents = _userAgents.Select(x => new UserAgentDocument
|
||||
{
|
||||
Id = !string.IsNullOrEmpty(x.Id) ? x.Id : Guid.NewGuid().ToString(),
|
||||
AgentId = x.AgentId,
|
||||
UserId = !string.IsNullOrEmpty(x.UserId) ? x.UserId : string.Empty,
|
||||
Editable = x.Editable,
|
||||
CreatedTime = x.CreatedTime,
|
||||
UpdatedTime = x.UpdatedTime
|
||||
}).ToList();
|
||||
|
||||
foreach (var userAgent in userAgents)
|
||||
{
|
||||
var filter = Builders<UserAgentDocument>.Filter.Eq(x => x.Id, userAgent.Id);
|
||||
var update = Builders<UserAgentDocument>.Update
|
||||
.Set(x => x.AgentId, userAgent.AgentId)
|
||||
.Set(x => x.UserId, userAgent.UserId)
|
||||
.Set(x => x.Editable, userAgent.Editable)
|
||||
.Set(x => x.CreatedTime, userAgent.CreatedTime)
|
||||
.Set(x => x.UpdatedTime, userAgent.UpdatedTime);
|
||||
_dc.UserAgents.UpdateOne(filter, update, _options);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return _changedTableNames.Count;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,3 +1,5 @@
|
|||
using BotSharp.Abstraction.Agents.Models;
|
||||
using BotSharp.Abstraction.Repositories.Filters;
|
||||
using BotSharp.Abstraction.Users.Enums;
|
||||
using BotSharp.Abstraction.Users.Models;
|
||||
|
||||
|
|
@ -168,4 +170,134 @@ public partial class MongoRepository
|
|||
UpdateUserIsDisable(userId, isDisable);
|
||||
}
|
||||
}
|
||||
|
||||
public PagedItems<User> GetUsers(UserFilter filter)
|
||||
{
|
||||
var userBuilder = Builders<UserDocument>.Filter;
|
||||
var userFilters = new List<FilterDefinition<UserDocument>>() { userBuilder.Empty };
|
||||
|
||||
// Apply filters
|
||||
if (!filter.UserIds.IsNullOrEmpty())
|
||||
{
|
||||
userFilters.Add(userBuilder.In(x => x.Id, filter.UserIds));
|
||||
}
|
||||
if (!filter.UserNames.IsNullOrEmpty())
|
||||
{
|
||||
userFilters.Add(userBuilder.In(x => x.UserName, filter.UserNames));
|
||||
}
|
||||
if (!filter.ExternalIds.IsNullOrEmpty())
|
||||
{
|
||||
userFilters.Add(userBuilder.In(x => x.ExternalId, filter.ExternalIds));
|
||||
}
|
||||
if (!filter.Roles.IsNullOrEmpty())
|
||||
{
|
||||
userFilters.Add(userBuilder.In(x => x.Role, filter.Roles));
|
||||
}
|
||||
if (!filter.Sources.IsNullOrEmpty())
|
||||
{
|
||||
userFilters.Add(userBuilder.In(x => x.Source, filter.Sources));
|
||||
}
|
||||
|
||||
// Filter def and sort
|
||||
var filterDef = userBuilder.And(userFilters);
|
||||
var sortDef = Builders<UserDocument>.Sort.Descending(x => x.CreatedTime);
|
||||
|
||||
// Search
|
||||
var userDocs = _dc.Users.Find(filterDef).Sort(sortDef).Skip(filter.Offset).Limit(filter.Size).ToList();
|
||||
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<string>(),
|
||||
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<User>
|
||||
{
|
||||
Items = users,
|
||||
Count = (int)count
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
public bool UpdateUser(User user, bool isUpdateUserAgents = false)
|
||||
{
|
||||
if (string.IsNullOrEmpty(user?.Id)) return false;
|
||||
|
||||
var userFilter = Builders<UserDocument>.Filter.Eq(x => x.Id, user.Id);
|
||||
var userUpdate = Builders<UserDocument>.Update
|
||||
.Set(x => x.Type, user.Type)
|
||||
.Set(x => x.Role, user.Role)
|
||||
.Set(x => x.Permissions, user.Permissions)
|
||||
.Set(x => x.UpdatedTime, DateTime.UtcNow);
|
||||
|
||||
_dc.Users.UpdateOne(userFilter, userUpdate);
|
||||
|
||||
if (isUpdateUserAgents)
|
||||
{
|
||||
var userAgentDocs = user.AgentActions?.Select(x => new UserAgentDocument
|
||||
{
|
||||
Id = !string.IsNullOrEmpty(x.Id) ? x.Id : Guid.NewGuid().ToString(),
|
||||
UserId = user.Id,
|
||||
AgentId = x.AgentId,
|
||||
Actions = x.Actions,
|
||||
CreatedTime = DateTime.UtcNow,
|
||||
UpdatedTime = DateTime.UtcNow
|
||||
})?.ToList() ?? [];
|
||||
|
||||
var toDelete = _dc.UserAgents.Find(Builders<UserAgentDocument>.Filter.And(
|
||||
Builders<UserAgentDocument>.Filter.Eq(x => x.UserId, user.Id),
|
||||
Builders<UserAgentDocument>.Filter.Nin(x => x.Id, userAgentDocs.Select(x => x.Id))
|
||||
)).ToList();
|
||||
|
||||
_dc.UserAgents.DeleteMany(Builders<UserAgentDocument>.Filter.In(x => x.Id, toDelete.Select(x => x.Id)));
|
||||
foreach (var doc in userAgentDocs)
|
||||
{
|
||||
var userAgentFilter = Builders<UserAgentDocument>.Filter.Eq(x => x.Id, doc.Id);
|
||||
var userAgentUpdate = Builders<UserAgentDocument>.Update
|
||||
.Set(x => x.Id, doc.Id)
|
||||
.Set(x => x.UserId, user.Id)
|
||||
.Set(x => x.AgentId, doc.AgentId)
|
||||
.Set(x => x.Actions, doc.Actions)
|
||||
.Set(x => x.UpdatedTime, DateTime.UtcNow);
|
||||
|
||||
_dc.UserAgents.UpdateOne(userAgentFilter, userAgentUpdate, _options);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,3 @@
|
|||
using BotSharp.Abstraction.Agents.Models;
|
||||
using BotSharp.Abstraction.Conversations.Models;
|
||||
using BotSharp.Abstraction.Users.Models;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace BotSharp.Plugin.MongoStorage.Repository;
|
||||
|
|
@ -26,9 +23,5 @@ public partial class MongoRepository : IBotSharpRepository
|
|||
};
|
||||
}
|
||||
|
||||
private List<Agent> _agents = new List<Agent>();
|
||||
private List<User> _users = new List<User>();
|
||||
private List<UserAgent> _userAgents = new List<UserAgent>();
|
||||
private List<Conversation> _conversations = new List<Conversation>();
|
||||
List<string> _changedTableNames = new List<string>();
|
||||
public IServiceProvider ServiceProvider => _services;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ global using BotSharp.Abstraction.Agents.Enums;
|
|||
global using BotSharp.Abstraction.Utilities;
|
||||
global using BotSharp.Abstraction.Plugins;
|
||||
global using BotSharp.Abstraction.Translation.Models;
|
||||
global using BotSharp.Abstraction.SideCar.Attributes;
|
||||
global using Microsoft.Extensions.Configuration;
|
||||
global using Microsoft.Extensions.DependencyInjection;
|
||||
global using MongoDB.Bson;
|
||||
|
|
|
|||
|
|
@ -92,7 +92,7 @@ public partial class TwoStageTaskPlanner : IRoutingPlaner
|
|||
}
|
||||
|
||||
var routing = _services.GetRequiredService<IRoutingService>();
|
||||
routing.ResetRecursiveCounter();
|
||||
routing.Context.ResetRecursiveCounter();
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -66,6 +66,7 @@ public partial class TencentCosService
|
|||
{
|
||||
MessageId = messageId,
|
||||
FileUrl = BuilFileUrl(file),
|
||||
FileDownloadUrl = BuilFileUrl(file),
|
||||
FileStorageUrl = file,
|
||||
FileName = fileName,
|
||||
FileExtension = fileExtension,
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@
|
|||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\tests\BotSharp.Plugin.PizzaBot\BotSharp.Plugin.PizzaBot.csproj" />
|
||||
<ProjectReference Include="..\BotSharp.ServiceDefaults\BotSharp.ServiceDefaults.csproj" />
|
||||
<ProjectReference Include="..\Infrastructure\BotSharp.Core.SideCar\BotSharp.Core.SideCar.csproj" />
|
||||
|
||||
</ItemGroup>
|
||||
|
||||
|
|
|
|||
|
|
@ -151,6 +151,12 @@
|
|||
}
|
||||
},
|
||||
|
||||
"SideCar": {
|
||||
"Conversation": {
|
||||
"Provider": "botsharp"
|
||||
}
|
||||
},
|
||||
|
||||
"WebBrowsing": {
|
||||
"Driver": "Playwright"
|
||||
},
|
||||
|
|
@ -321,6 +327,7 @@
|
|||
"PluginLoader": {
|
||||
"Assemblies": [
|
||||
"BotSharp.Core",
|
||||
"BotSharp.Core.SideCar",
|
||||
"BotSharp.Logger",
|
||||
"BotSharp.Plugin.MongoStorage",
|
||||
"BotSharp.Plugin.Dashboard",
|
||||
|
|
|
|||
Loading…
Reference in a new issue