Merge branch 'SciSharp:master' into master
This commit is contained in:
commit
608f0f0be4
|
|
@ -2,7 +2,7 @@
|
|||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<LangVersion>12.0</LangVersion>
|
||||
<BotSharpVersion>3.0.0</BotSharpVersion>
|
||||
<BotSharpVersion>4.0.0</BotSharpVersion>
|
||||
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
|
||||
<GenerateDocumentationFile>false</GenerateDocumentationFile>
|
||||
</PropertyGroup>
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ It's written in C# running on .Net Core that is full cross-platform framework, t
|
|||
* Built-in multi-agents and conversation with state management.
|
||||
* Support multiple LLM Planning approaches to handle different tasks from simple to complex.
|
||||
* Built-in RAG related interfaces, Memory based vector searching.
|
||||
* Support multiple AI platforms (ChatGPT 3.5 / 4.0, PaLM 2, LLaMA 3, Claude Sonnet 3.5, HuggingFace).
|
||||
* Support multiple AI platforms (ChatGPT 3.5/ 4o/ o1, Gemini 2, LLaMA 3, Claude Sonnet 3.5, HuggingFace).
|
||||
* Allow multiple agents with different responsibilities cooperate to complete complex tasks.
|
||||
* Build, test, evaluate and audit your LLM agent in one place.
|
||||
* Build-in `BotSharp UI` written in [SvelteKit](https://kit.svelte.dev/).
|
||||
|
|
@ -79,6 +79,7 @@ BotSharp uses component design, the kernel is kept to a minimum, and business fu
|
|||
#### Data Storages
|
||||
- BotSharp.Core.Repository
|
||||
- BotSharp.Plugin.MongoStorage
|
||||
- [BotSharp.Plugin.LiteDBStorage](https://github.com/GreenShadeZhang/BotSharp/tree/dev_litedb/src/Plugins/BotSharp.Plugin.LiteDBStorage)
|
||||
- BotSharp.Plugin.TencentCos
|
||||
|
||||
#### LLMs
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ public enum AgentField
|
|||
LlmConfig,
|
||||
Utility,
|
||||
KnowledgeBase,
|
||||
Rule,
|
||||
MaxMessageCount
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,6 @@
|
|||
namespace BotSharp.Abstraction.Agents;
|
||||
|
||||
public interface IAgentRuleHook
|
||||
{
|
||||
void AddRules(List<AgentRule> rules);
|
||||
}
|
||||
|
|
@ -58,6 +58,4 @@ public interface IAgentService
|
|||
Task<List<UserAgent>> GetUserAgents(string userId);
|
||||
|
||||
PluginDef GetPlugin(string agentId);
|
||||
|
||||
IEnumerable<AgentUtility> GetAgentUtilityOptions();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -99,6 +99,11 @@ public class Agent
|
|||
/// </summary>
|
||||
public List<AgentUtility> Utilities { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Agent rules
|
||||
/// </summary>
|
||||
public List<AgentRule> Rules { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Agent knowledge bases
|
||||
/// </summary>
|
||||
|
|
@ -154,6 +159,7 @@ public class Agent
|
|||
MaxMessageCount = agent.MaxMessageCount,
|
||||
Profiles = agent.Profiles,
|
||||
RoutingRules = agent.RoutingRules,
|
||||
Rules = agent.Rules,
|
||||
LlmConfig = agent.LlmConfig,
|
||||
KnowledgeBases = agent.KnowledgeBases,
|
||||
CreatedDateTime = agent.CreatedDateTime,
|
||||
|
|
@ -269,6 +275,12 @@ public class Agent
|
|||
return this;
|
||||
}
|
||||
|
||||
public Agent SetRules(List<AgentRule> rules)
|
||||
{
|
||||
Rules = rules ?? [];
|
||||
return this;
|
||||
}
|
||||
|
||||
public Agent SetLlmConfig(AgentLlmConfig? llmConfig)
|
||||
{
|
||||
LlmConfig = llmConfig;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,13 @@
|
|||
namespace BotSharp.Abstraction.Agents.Models;
|
||||
|
||||
public class AgentRule
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public bool Disabled { get; set; }
|
||||
|
||||
[JsonPropertyName("event_name")]
|
||||
public string EventName { get; set; }
|
||||
|
||||
[JsonPropertyName("entity_type")]
|
||||
public string EntityType { get; set; }
|
||||
}
|
||||
|
|
@ -28,4 +28,5 @@ public interface IWebBrowser
|
|||
Task<BrowserActionResult> CloseCurrentPage(MessageInfo message);
|
||||
Task<BrowserActionResult> SendHttpRequest(MessageInfo message, HttpRequestParams actionParams);
|
||||
Task<BrowserActionResult> GetAttributeValue(MessageInfo message, ElementLocatingArgs location);
|
||||
Task<BrowserActionResult> SetAttributeValue(MessageInfo message, ElementLocatingArgs location);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,4 +3,6 @@ namespace BotSharp.Abstraction.Browsing.Models;
|
|||
public class BrowserActionArgs
|
||||
{
|
||||
public bool Headless { get; set; }
|
||||
public string? UserDataDir { get; set; }
|
||||
public string? RemoteHostUrl { get; set; }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,6 +8,10 @@ public class BrowserActionResult
|
|||
public string? StackTrace { get; set; }
|
||||
public string? Selector { get; set; }
|
||||
public string? Body { get; set; }
|
||||
/// <summary>
|
||||
/// Page open in new tab after button click
|
||||
/// </summary>
|
||||
public string? UrlAfterAction { get; set; }
|
||||
public bool IsHighlighted { get; set; }
|
||||
|
||||
public override string ToString()
|
||||
|
|
|
|||
|
|
@ -0,0 +1,8 @@
|
|||
using System.Net.Http.Headers;
|
||||
|
||||
namespace BotSharp.Abstraction.Http;
|
||||
|
||||
public interface IHttpRequestHook
|
||||
{
|
||||
void OnAddHttpHeaders(HttpHeaders headers);
|
||||
}
|
||||
|
|
@ -33,6 +33,7 @@ public interface IBotSharpRepository : IHaveServiceProvider
|
|||
List<User> GetUserByIds(List<string> ids) => throw new NotImplementedException();
|
||||
List<User> GetUsersByAffiliateId(string affiliateId) => throw new NotImplementedException();
|
||||
User? GetUserByUserName(string userName) => throw new NotImplementedException();
|
||||
void UpdateUserName(string userId, string userName) => throw new NotImplementedException();
|
||||
Dashboard? GetDashboard(string id = null) => throw new NotImplementedException();
|
||||
void CreateUser(User user) => throw new NotImplementedException();
|
||||
void UpdateExistUser(string userId, User user) => throw new NotImplementedException();
|
||||
|
|
@ -49,6 +50,7 @@ public interface IBotSharpRepository : IHaveServiceProvider
|
|||
PagedItems<User> GetUsers(UserFilter filter) => throw new NotImplementedException();
|
||||
User? GetUserDetails(string userId, bool includeAgent = false) => throw new NotImplementedException();
|
||||
bool UpdateUser(User user, bool updateUserAgents = false) => throw new NotImplementedException();
|
||||
|
||||
#endregion
|
||||
|
||||
#region Agent
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
using BotSharp.Abstraction.Users.Models;
|
||||
using System.IdentityModel.Tokens.Jwt;
|
||||
using System.Security.Claims;
|
||||
|
||||
namespace BotSharp.Abstraction.Users;
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ public interface IUserService
|
|||
Task<Token?> GetAffiliateToken(string authorization);
|
||||
Task<Token?> GetAdminToken(string authorization);
|
||||
Task<Token?> GetToken(string authorization);
|
||||
Task<Token> CreateTokenByUser(User user);
|
||||
Task<User> GetMyProfile();
|
||||
Task<bool> VerifyUserNameExisting(string userName);
|
||||
Task<bool> VerifyEmailExisting(string email);
|
||||
|
|
|
|||
|
|
@ -23,7 +23,6 @@ public class User
|
|||
public bool Verified { get; set; }
|
||||
public string RegionCode { get; set; } = "CN";
|
||||
public string? AffiliateId { get; set; }
|
||||
public string? ReferralCode { get; set; }
|
||||
public string? EmployeeId { get; set; }
|
||||
public bool IsDisabled { get; set; }
|
||||
public IEnumerable<string> Permissions { get; set; } = [];
|
||||
|
|
|
|||
|
|
@ -4,4 +4,5 @@ public class UserActivationModel
|
|||
{
|
||||
public string UserName { get; set; }
|
||||
public string VerificationCode { get; set; }
|
||||
public string RegionCode { get; set; } = "CN";
|
||||
}
|
||||
|
|
|
|||
|
|
@ -40,6 +40,7 @@ public partial class AgentService
|
|||
record.Samples = agent.Samples ?? [];
|
||||
record.Utilities = agent.Utilities ?? [];
|
||||
record.KnowledgeBases = agent.KnowledgeBases ?? [];
|
||||
record.Rules = agent.Rules ?? [];
|
||||
if (agent.LlmConfig != null && !agent.LlmConfig.IsInherit)
|
||||
{
|
||||
record.LlmConfig = agent.LlmConfig;
|
||||
|
|
@ -104,6 +105,7 @@ public partial class AgentService
|
|||
.SetSamples(foundAgent.Samples)
|
||||
.SetUtilities(foundAgent.Utilities)
|
||||
.SetKnowledgeBases(foundAgent.KnowledgeBases)
|
||||
.SetRules(foundAgent.Rules)
|
||||
.SetLlmConfig(foundAgent.LlmConfig);
|
||||
|
||||
_db.UpdateAgent(clonedAgent, AgentField.All);
|
||||
|
|
|
|||
|
|
@ -53,15 +53,4 @@ public partial class AgentService : IAgentService
|
|||
var userAgents = _db.GetUserAgents(userId);
|
||||
return userAgents;
|
||||
}
|
||||
|
||||
public IEnumerable<AgentUtility> GetAgentUtilityOptions()
|
||||
{
|
||||
var utilities = new List<AgentUtility>();
|
||||
var hooks = _services.GetServices<IAgentUtilityHook>();
|
||||
foreach (var hook in hooks)
|
||||
{
|
||||
hook.AddUtilities(utilities);
|
||||
}
|
||||
return utilities.Where(x => !string.IsNullOrWhiteSpace(x.Name)).OrderBy(x => x.Name).ToList();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -190,10 +190,10 @@
|
|||
<ItemGroup>
|
||||
<PackageReference Include="Aspects.Cache" Version="2.0.4" />
|
||||
<PackageReference Include="DistributedLock.Redis" Version="1.0.3" />
|
||||
<PackageReference Include="EntityFrameworkCore.BootKit" Version="8.6.0" />
|
||||
<PackageReference Include="EntityFrameworkCore.BootKit" Version="8.8.0" />
|
||||
<PackageReference Include="Fluid.Core" Version="2.11.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="8.0.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Http" Version="8.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Http" Version="8.0.1" />
|
||||
<PackageReference Include="Nanoid" Version="3.1.0" />
|
||||
</ItemGroup>
|
||||
|
||||
|
|
|
|||
|
|
@ -105,6 +105,11 @@ public static class BotSharpCoreExtensions
|
|||
var dbSettings = new BotSharpDatabaseSettings();
|
||||
config.Bind("Database", dbSettings);
|
||||
|
||||
if (string.IsNullOrEmpty(dbSettings.Redis))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
services.AddSingleton<IConnectionMultiplexer>(ConnectionMultiplexer.Connect(dbSettings.Redis));
|
||||
services.AddSingleton<IEventPublisher, RedisPublisher>();
|
||||
services.AddSingleton<IEventSubscriber, RedisSubscriber>();
|
||||
|
|
|
|||
|
|
@ -6,12 +6,12 @@ namespace BotSharp.Core.Infrastructures;
|
|||
|
||||
public class DistributedLocker : IDistributedLocker
|
||||
{
|
||||
private readonly IConnectionMultiplexer _redis;
|
||||
private readonly IServiceProvider _services;
|
||||
private readonly ILogger _logger;
|
||||
|
||||
public DistributedLocker(IConnectionMultiplexer redis, ILogger<DistributedLocker> logger)
|
||||
public DistributedLocker(IServiceProvider services, ILogger<DistributedLocker> logger)
|
||||
{
|
||||
_redis = redis;
|
||||
_services = services;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
|
|
@ -19,7 +19,8 @@ public class DistributedLocker : IDistributedLocker
|
|||
{
|
||||
var timeout = TimeSpan.FromSeconds(timeoutInSeconds);
|
||||
|
||||
var @lock = new RedisDistributedLock(resource, _redis.GetDatabase());
|
||||
var redis = _services.GetRequiredService<IConnectionMultiplexer>();
|
||||
var @lock = new RedisDistributedLock(resource, redis.GetDatabase());
|
||||
await using (var handle = await @lock.TryAcquireAsync(timeout))
|
||||
{
|
||||
if (handle == null)
|
||||
|
|
@ -37,7 +38,8 @@ public class DistributedLocker : IDistributedLocker
|
|||
{
|
||||
var timeout = TimeSpan.FromSeconds(timeoutInSeconds);
|
||||
|
||||
var @lock = new RedisDistributedLock(resource, _redis.GetDatabase());
|
||||
var redis = _services.GetRequiredService<IConnectionMultiplexer>();
|
||||
var @lock = new RedisDistributedLock(resource, redis.GetDatabase());
|
||||
using (var handle = @lock.TryAcquire(timeout))
|
||||
{
|
||||
if (handle == null)
|
||||
|
|
|
|||
|
|
@ -39,10 +39,7 @@ public class RedisPublisher : IEventPublisher
|
|||
|
||||
// Add a message to the stream, keeping only the latest 1 million messages
|
||||
var messageId = await db.StreamAddAsync(channel,
|
||||
[
|
||||
new NameValueEntry("message", message),
|
||||
new NameValueEntry("timestamp", DateTime.UtcNow.ToString("o"))
|
||||
],
|
||||
AssembleMessage(message),
|
||||
maxLength: 1000 * 10000);
|
||||
|
||||
_logger.LogInformation($"Published message {channel} {message} ({messageId})");
|
||||
|
|
@ -82,6 +79,25 @@ public class RedisPublisher : IEventPublisher
|
|||
return exists;
|
||||
}
|
||||
|
||||
public static NameValueEntry[] AssembleMessage(RedisValue message)
|
||||
{
|
||||
return
|
||||
[
|
||||
new NameValueEntry("message", message),
|
||||
new NameValueEntry("timestamp", DateTime.UtcNow.ToString("o"))
|
||||
];
|
||||
}
|
||||
|
||||
public static NameValueEntry[] AssembleErrorMessage(RedisValue message, string error)
|
||||
{
|
||||
return
|
||||
[
|
||||
new NameValueEntry("message", message),
|
||||
new NameValueEntry("timestamp", DateTime.UtcNow.ToString("o")),
|
||||
new NameValueEntry("error", error)
|
||||
];
|
||||
}
|
||||
|
||||
public async Task ReDispatchAsync(string channel, int count = 10, string order = "asc")
|
||||
{
|
||||
var db = _redis.GetDatabase();
|
||||
|
|
@ -93,10 +109,10 @@ public class RedisPublisher : IEventPublisher
|
|||
|
||||
try
|
||||
{
|
||||
var messageId = await db.StreamAddAsync(channel, [
|
||||
new NameValueEntry("message", entry.Values[0].Value),
|
||||
new NameValueEntry("timestamp", DateTime.UtcNow.ToString("o"))
|
||||
]);
|
||||
var message = entry.Values.First(x => x.Name == "message").Value;
|
||||
var messageId = await db.StreamAddAsync(channel,
|
||||
AssembleMessage(message),
|
||||
maxLength: 1000 * 10000);
|
||||
|
||||
_logger.LogWarning($"ReDispatched message: {channel} {entry.Values[0].Value} ({messageId})");
|
||||
|
||||
|
|
@ -164,20 +180,7 @@ public class RedisPublisher : IEventPublisher
|
|||
var db = _redis.GetDatabase();
|
||||
|
||||
var entries = await db.StreamRangeAsync(channel, "-", "+", count: count, messageOrder: Order.Ascending);
|
||||
foreach (var entry in entries)
|
||||
{
|
||||
_logger.LogInformation($"Fetched message: {channel} {entry.Values[0].Value} ({entry.Id})");
|
||||
|
||||
try
|
||||
{
|
||||
await db.StreamDeleteAsync(channel, [entry.Id]);
|
||||
|
||||
_logger.LogWarning($"Deleted message: {channel} {entry.Values[0].Value} ({entry.Id})");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError($"Error processing message: {ex.Message}, event id: {channel} {entry.Id}\r\n{ex}");
|
||||
}
|
||||
}
|
||||
var deletedCount = await db.StreamDeleteAsync(channel, entries.Select(x => x.Id).ToArray());
|
||||
_logger.LogWarning($"Deleted {deletedCount} messages from Redis stream {channel}");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -42,6 +42,8 @@ public class RedisSubscriber : IEventSubscriber
|
|||
await CreateConsumerGroup(db, channel, group);
|
||||
}
|
||||
|
||||
await CreateConsumerGroup(db, $"{channel}-Error", group);
|
||||
|
||||
var consumer = Environment.MachineName;
|
||||
if (port.HasValue)
|
||||
{
|
||||
|
|
@ -58,28 +60,36 @@ public class RedisSubscriber : IEventSubscriber
|
|||
break;
|
||||
}
|
||||
|
||||
if (priorityEnabled)
|
||||
try
|
||||
{
|
||||
if (await HandleGroupMessage(db, $"{channel}-{EventPriority.High}", group, consumer, received) > 0)
|
||||
if (priorityEnabled)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (await HandleGroupMessage(db, $"{channel}-{EventPriority.High}", group, consumer, received, $"{channel}-Error") > 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (await HandleGroupMessage(db, $"{channel}-{EventPriority.Medium}", group, consumer, received) > 0)
|
||||
if (await HandleGroupMessage(db, $"{channel}-{EventPriority.Medium}", group, consumer, received, $"{channel}-Error") > 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
await HandleGroupMessage(db, $"{channel}-{EventPriority.Low}", group, consumer, received, $"{channel}-Error");
|
||||
}
|
||||
else
|
||||
{
|
||||
continue;
|
||||
await HandleGroupMessage(db, channel, group, consumer, received, $"{channel}-Error");
|
||||
}
|
||||
|
||||
await HandleGroupMessage(db, $"{channel}-{EventPriority.Low}", group, consumer, received);
|
||||
}
|
||||
else
|
||||
catch (Exception ex)
|
||||
{
|
||||
await HandleGroupMessage(db, channel, group, consumer, received);
|
||||
_logger.LogError($"Error processing message: {ex.Message}\r\n{ex}");
|
||||
await Task.Delay(1000 * 60);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<int> HandleGroupMessage(IDatabase db, string channel, string group, string consumer, Func<string, string, Task> received)
|
||||
private async Task<int> HandleGroupMessage(IDatabase db, string channel, string group, string consumer, Func<string, string, Task> received, string errorChannel)
|
||||
{
|
||||
var entries = await db.StreamReadGroupAsync(channel, group, consumer, count: 1);
|
||||
foreach (var entry in entries)
|
||||
|
|
@ -90,13 +100,24 @@ public class RedisSubscriber : IEventSubscriber
|
|||
try
|
||||
{
|
||||
await received(channel, entry.Values[0].Value);
|
||||
|
||||
// Optionally delete the message to save space
|
||||
await db.StreamDeleteAsync(channel, [entry.Id]);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError($"Error processing message: {ex.Message}, event id: {channel} {entry.Id}\r\n{ex}");
|
||||
_logger.LogError($"Error processing message: {ex.Message}, event id: {channel} {entry.Id} {entry.Values[0].Value}");
|
||||
|
||||
// Add a message to the Error stream, keeping only the latest 1 million messages
|
||||
await db.StreamAddAsync(errorChannel,
|
||||
RedisPublisher.AssembleErrorMessage(entry.Values[0].Value, ex.Message),
|
||||
messageId: entry.Id,
|
||||
maxLength: 1000 * 10000);
|
||||
|
||||
// Slow down the consumer if there are errors
|
||||
await Task.Delay(1000 * 10);
|
||||
}
|
||||
finally
|
||||
{
|
||||
var deletedCount = await db.StreamDeleteAsync(channel, [entry.Id]);
|
||||
_logger.LogInformation($"Handled message {entry.Id}: {deletedCount == 1}");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -60,6 +60,9 @@ namespace BotSharp.Core.Repository
|
|||
case AgentField.KnowledgeBase:
|
||||
UpdateAgentKnowledgeBases(agent.Id, agent.KnowledgeBases);
|
||||
break;
|
||||
case AgentField.Rule:
|
||||
UpdateAgentRules(agent.Id, agent.Rules);
|
||||
break;
|
||||
case AgentField.MaxMessageCount:
|
||||
UpdateAgentMaxMessageCount(agent.Id, agent.MaxMessageCount);
|
||||
break;
|
||||
|
|
@ -184,6 +187,19 @@ namespace BotSharp.Core.Repository
|
|||
File.WriteAllText(agentFile, json);
|
||||
}
|
||||
|
||||
private void UpdateAgentRules(string agentId, List<AgentRule> rules)
|
||||
{
|
||||
if (rules == null) return;
|
||||
|
||||
var (agent, agentFile) = GetAgentFromFile(agentId);
|
||||
if (agent == null) return;
|
||||
|
||||
agent.Rules = rules;
|
||||
agent.UpdatedDateTime = DateTime.UtcNow;
|
||||
var json = JsonSerializer.Serialize(agent, _options);
|
||||
File.WriteAllText(agentFile, json);
|
||||
}
|
||||
|
||||
private void UpdateAgentRoutingRules(string agentId, List<RoutingRule> rules)
|
||||
{
|
||||
if (rules == null) return;
|
||||
|
|
@ -240,7 +256,7 @@ namespace BotSharp.Core.Repository
|
|||
var text = JsonSerializer.Serialize(func, _options);
|
||||
var file = Path.Combine(functionDir, $"{func.Name}.json");
|
||||
File.WriteAllText(file, text);
|
||||
Thread.Sleep(100);
|
||||
Thread.Sleep(50);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -328,6 +344,7 @@ namespace BotSharp.Core.Repository
|
|||
agent.Utilities = inputAgent.Utilities;
|
||||
agent.KnowledgeBases = inputAgent.KnowledgeBases;
|
||||
agent.RoutingRules = inputAgent.RoutingRules;
|
||||
agent.Rules = inputAgent.Rules;
|
||||
agent.LlmConfig = inputAgent.LlmConfig;
|
||||
agent.MaxMessageCount = inputAgent.MaxMessageCount;
|
||||
agent.UpdatedDateTime = DateTime.UtcNow;
|
||||
|
|
|
|||
|
|
@ -62,7 +62,7 @@ public class UserService : IUserService
|
|||
|
||||
if (!string.IsNullOrWhiteSpace(user.Phone))
|
||||
{
|
||||
record = db.GetUserByPhone(user.Phone);
|
||||
record = db.GetUserByPhone(user.Phone, regionCode: (string.IsNullOrWhiteSpace(user.RegionCode) ? "CN" : user.RegionCode));
|
||||
}
|
||||
|
||||
if (record == null && !string.IsNullOrWhiteSpace(user.Email))
|
||||
|
|
@ -112,7 +112,7 @@ public class UserService : IUserService
|
|||
db.UpdateExistUser(hasRegisterId, record);
|
||||
}
|
||||
|
||||
_logger.LogWarning($"Created new user account: {record.Id} {record.UserName}");
|
||||
_logger.LogWarning($"Created new user account: {record.Id} {record.UserName}, RegionCode: {record.RegionCode}");
|
||||
Utilities.ClearCache();
|
||||
|
||||
var hooks = _services.GetServices<IAuthenticationHook>();
|
||||
|
|
@ -127,7 +127,8 @@ public class UserService : IUserService
|
|||
public async Task<bool> UpdatePassword(string password, string verificationCode)
|
||||
{
|
||||
var db = _services.GetRequiredService<IBotSharpRepository>();
|
||||
var record = db.GetUserByUserName(_user.UserName);
|
||||
|
||||
var record = db.GetUserById(_user.Id);
|
||||
|
||||
if (record == null)
|
||||
{
|
||||
|
|
@ -473,7 +474,7 @@ public class UserService : IUserService
|
|||
var record = id.Contains("@") ? db.GetUserByEmail(id) : db.GetUserByUserName(id);
|
||||
if (record == null)
|
||||
{
|
||||
record = db.GetUserByPhone(id);
|
||||
record = db.GetUserByPhone(id, regionCode: (string.IsNullOrWhiteSpace(model.RegionCode) ? "CN" : model.RegionCode));
|
||||
}
|
||||
|
||||
if (record == null)
|
||||
|
|
@ -505,6 +506,20 @@ public class UserService : IUserService
|
|||
return token;
|
||||
}
|
||||
|
||||
public async Task<Token> CreateTokenByUser(User user)
|
||||
{
|
||||
var accessToken = GenerateJwtToken(user);
|
||||
var jwt = new JwtSecurityTokenHandler().ReadJwtToken(accessToken);
|
||||
var token = new Token
|
||||
{
|
||||
AccessToken = accessToken,
|
||||
ExpireTime = jwt.Payload.Exp.Value,
|
||||
TokenType = "Bearer",
|
||||
Scope = "api"
|
||||
};
|
||||
return token;
|
||||
}
|
||||
|
||||
public async Task<bool> VerifyUserNameExisting(string userName)
|
||||
{
|
||||
if (string.IsNullOrEmpty(userName))
|
||||
|
|
@ -646,7 +661,7 @@ public class UserService : IUserService
|
|||
|
||||
if (!string.IsNullOrEmpty(user.Phone))
|
||||
{
|
||||
record = db.GetUserByPhone(user.Phone);
|
||||
record = db.GetUserByPhone(user.Phone, regionCode: (string.IsNullOrWhiteSpace(user.RegionCode) ? "CN" : user.RegionCode));
|
||||
}
|
||||
|
||||
if (record == null)
|
||||
|
|
@ -695,7 +710,7 @@ public class UserService : IUserService
|
|||
var curUser = await GetMyProfile();
|
||||
var db = _services.GetRequiredService<IBotSharpRepository>();
|
||||
var record = db.GetUserById(curUser.Id);
|
||||
var existPhone = db.GetUserByPhone(phone);
|
||||
var existPhone = db.GetUserByPhone(phone, regionCode: regionCode);
|
||||
|
||||
if (record == null || (existPhone != null && existPhone.RegionCode == regionCode))
|
||||
{
|
||||
|
|
|
|||
|
|
@ -152,6 +152,24 @@ public class AgentController : ControllerBase
|
|||
[HttpGet("/agent/utility/options")]
|
||||
public IEnumerable<AgentUtility> GetAgentUtilityOptions()
|
||||
{
|
||||
return _agentService.GetAgentUtilityOptions();
|
||||
var utilities = new List<AgentUtility>();
|
||||
var hooks = _services.GetServices<IAgentUtilityHook>();
|
||||
foreach (var hook in hooks)
|
||||
{
|
||||
hook.AddUtilities(utilities);
|
||||
}
|
||||
return utilities.Where(x => !string.IsNullOrWhiteSpace(x.Name)).OrderBy(x => x.Name).ToList();
|
||||
}
|
||||
|
||||
[HttpGet("/agent/rule/options")]
|
||||
public IEnumerable<AgentRule> GetAgentRuleOptions()
|
||||
{
|
||||
var rules = new List<AgentRule>();
|
||||
var hooks = _services.GetServices<IAgentRuleHook>();
|
||||
foreach (var hook in hooks)
|
||||
{
|
||||
hook.AddRules(rules);
|
||||
}
|
||||
return rules.Where(x => !string.IsNullOrWhiteSpace(x.Name)).OrderBy(x => x.Name).ToList();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,3 @@
|
|||
using BotSharp.Abstraction.Users.Enums;
|
||||
using BotSharp.Abstraction.Users.Settings;
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
using Microsoft.AspNetCore.Authentication.Cookies;
|
||||
|
|
@ -244,7 +243,7 @@ public class UserController : ControllerBase
|
|||
var file = fileStorage.GetUserAvatar();
|
||||
if (string.IsNullOrEmpty(file))
|
||||
{
|
||||
return NotFound();
|
||||
return NoContent();
|
||||
}
|
||||
return BuildFileResult(file);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -55,6 +55,7 @@ public class AgentCreationModel
|
|||
public List<AgentUtility> Utilities { get; set; } = new();
|
||||
public List<RoutingRuleUpdateModel> RoutingRules { get; set; } = new();
|
||||
public List<AgentKnowledgeBase> KnowledgeBases { get; set; } = new();
|
||||
public List<AgentRule> Rules { get; set; } = new();
|
||||
public AgentLlmConfig? LlmConfig { get; set; }
|
||||
|
||||
public Agent ToAgent()
|
||||
|
|
@ -78,6 +79,7 @@ public class AgentCreationModel
|
|||
Profiles = Profiles,
|
||||
LlmConfig = LlmConfig,
|
||||
KnowledgeBases = KnowledgeBases,
|
||||
Rules = Rules,
|
||||
RoutingRules = RoutingRules?.Select(x => RoutingRuleUpdateModel.ToDomainElement(x))?.ToList() ?? [],
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -75,6 +75,9 @@ public class AgentUpdateModel
|
|||
[JsonPropertyName("routing_rules")]
|
||||
public List<RoutingRuleUpdateModel>? RoutingRules { get; set; }
|
||||
|
||||
[JsonPropertyName("rules")]
|
||||
public List<AgentRule>? Rules { get; set; }
|
||||
|
||||
[JsonPropertyName("llm_config")]
|
||||
public AgentLlmConfig? LlmConfig { get; set; }
|
||||
|
||||
|
|
@ -89,15 +92,16 @@ public class AgentUpdateModel
|
|||
MergeUtility = MergeUtility,
|
||||
MaxMessageCount = MaxMessageCount,
|
||||
Type = Type,
|
||||
Profiles = Profiles ?? new List<string>(),
|
||||
RoutingRules = RoutingRules?.Select(x => RoutingRuleUpdateModel.ToDomainElement(x))?.ToList() ?? new List<RoutingRule>(),
|
||||
Profiles = Profiles ?? [],
|
||||
RoutingRules = RoutingRules?.Select(x => RoutingRuleUpdateModel.ToDomainElement(x))?.ToList() ?? [],
|
||||
Instruction = Instruction ?? string.Empty,
|
||||
ChannelInstructions = ChannelInstructions ?? new List<ChannelInstruction>(),
|
||||
Templates = Templates ?? new List<AgentTemplate>(),
|
||||
Functions = Functions ?? new List<FunctionDef>(),
|
||||
Responses = Responses ?? new List<AgentResponse>(),
|
||||
Utilities = Utilities ?? new List<AgentUtility>(),
|
||||
ChannelInstructions = ChannelInstructions ?? [],
|
||||
Templates = Templates ?? [],
|
||||
Functions = Functions ?? [],
|
||||
Responses = Responses ?? [],
|
||||
Utilities = Utilities ?? [],
|
||||
KnowledgeBases = KnowledgeBases ?? [],
|
||||
Rules = Rules ?? [],
|
||||
LlmConfig = LlmConfig
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -28,6 +28,9 @@ public class AgentViewModel
|
|||
[JsonPropertyName("knowledge_bases")]
|
||||
public List<AgentKnowledgeBase> KnowledgeBases { get; set; }
|
||||
|
||||
[JsonPropertyName("rules")]
|
||||
public List<AgentRule> Rules { get; set; }
|
||||
|
||||
[JsonPropertyName("is_public")]
|
||||
public bool IsPublic { get; set; }
|
||||
|
||||
|
|
@ -72,20 +75,21 @@ public class AgentViewModel
|
|||
Description = agent.Description,
|
||||
Type = agent.Type,
|
||||
Instruction = agent.Instruction,
|
||||
ChannelInstructions = agent.ChannelInstructions,
|
||||
Templates = agent.Templates,
|
||||
Functions = agent.Functions,
|
||||
Responses = agent.Responses,
|
||||
Samples = agent.Samples,
|
||||
Utilities = agent.Utilities,
|
||||
KnowledgeBases = agent.KnowledgeBases,
|
||||
ChannelInstructions = agent.ChannelInstructions ?? [],
|
||||
Templates = agent.Templates ?? [],
|
||||
Functions = agent.Functions ?? [],
|
||||
Responses = agent.Responses ?? [],
|
||||
Samples = agent.Samples ?? [],
|
||||
Utilities = agent.Utilities ?? [],
|
||||
KnowledgeBases = agent.KnowledgeBases ?? [],
|
||||
IsPublic= agent.IsPublic,
|
||||
Disabled = agent.Disabled,
|
||||
MergeUtility = agent.MergeUtility,
|
||||
IconUrl = agent.IconUrl,
|
||||
MaxMessageCount = agent.MaxMessageCount,
|
||||
Profiles = agent.Profiles ?? new List<string>(),
|
||||
RoutingRules = agent.RoutingRules,
|
||||
Profiles = agent.Profiles ?? [],
|
||||
RoutingRules = agent.RoutingRules ?? [],
|
||||
Rules = agent.Rules ?? [],
|
||||
LlmConfig = agent.LlmConfig,
|
||||
Plugin = agent.Plugin,
|
||||
CreatedDateTime = agent.CreatedDateTime,
|
||||
|
|
|
|||
|
|
@ -13,7 +13,6 @@ public class UserCreationModel
|
|||
public string Type { get; set; } = UserType.Client;
|
||||
public string Role { get; set; } = UserRole.User;
|
||||
public string RegionCode { get; set; } = "CN";
|
||||
public string? ReferralCode { get; set; }
|
||||
public User ToUser()
|
||||
{
|
||||
return new User
|
||||
|
|
@ -27,7 +26,6 @@ public class UserCreationModel
|
|||
Role = Role,
|
||||
Type = Type,
|
||||
RegionCode = RegionCode,
|
||||
ReferralCode = ReferralCode
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -33,10 +33,6 @@
|
|||
</Content>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.Http" Version="8.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\Infrastructure\BotSharp.Core\BotSharp.Core.csproj" />
|
||||
</ItemGroup>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
using System.Net.Http;
|
||||
using System.Net.Mime;
|
||||
using BotSharp.Abstraction.Http;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
|
|
@ -13,7 +14,6 @@ public class HandleHttpRequestFn : IFunctionCallback
|
|||
private readonly IServiceProvider _services;
|
||||
private readonly ILogger<HandleHttpRequestFn> _logger;
|
||||
private readonly IHttpClientFactory _httpClientFactory;
|
||||
private readonly IHttpContextAccessor _context;
|
||||
private readonly BotSharpOptions _options;
|
||||
|
||||
public HandleHttpRequestFn(IServiceProvider services,
|
||||
|
|
@ -25,7 +25,6 @@ public class HandleHttpRequestFn : IFunctionCallback
|
|||
_services = services;
|
||||
_logger = logger;
|
||||
_httpClientFactory = httpClientFactory;
|
||||
_context = context;
|
||||
_options = options;
|
||||
}
|
||||
|
||||
|
|
@ -46,7 +45,7 @@ public class HandleHttpRequestFn : IFunctionCallback
|
|||
catch (Exception ex)
|
||||
{
|
||||
var msg = $"Fail when sending http request. Url: {url}, method: {method}, content: {content}";
|
||||
_logger.LogWarning($"{msg}\n(Error: {ex.Message})");
|
||||
_logger.LogError($"{msg}\n(Error: {ex.Message}\r\n{ex.InnerException})");
|
||||
message.Content = msg;
|
||||
return false;
|
||||
}
|
||||
|
|
@ -57,7 +56,7 @@ public class HandleHttpRequestFn : IFunctionCallback
|
|||
if (string.IsNullOrEmpty(url)) return null;
|
||||
|
||||
using var client = _httpClientFactory.CreateClient();
|
||||
AddRequestHeaders(client);
|
||||
PrepareRequestHeaders(client);
|
||||
|
||||
var (uri, request) = BuildHttpRequest(url, method, content);
|
||||
var response = await client.SendAsync(request);
|
||||
|
|
@ -69,15 +68,12 @@ public class HandleHttpRequestFn : IFunctionCallback
|
|||
return response;
|
||||
}
|
||||
|
||||
private void AddRequestHeaders(HttpClient client)
|
||||
private void PrepareRequestHeaders(HttpClient client)
|
||||
{
|
||||
client.DefaultRequestHeaders.Add("Authorization", $"{_context.HttpContext.Request.Headers["Authorization"]}");
|
||||
|
||||
var settings = _services.GetRequiredService<HttpHandlerSettings>();
|
||||
var origin = !string.IsNullOrEmpty(settings.Origin) ? settings.Origin : $"{_context.HttpContext.Request.Headers["Origin"]}";
|
||||
if (!string.IsNullOrEmpty(origin))
|
||||
var hooks = _services.GetServices<IHttpRequestHook>();
|
||||
foreach (var hook in hooks)
|
||||
{
|
||||
client.DefaultRequestHeaders.Add("Origin", origin);
|
||||
hook.OnAddHttpHeaders(client.DefaultRequestHeaders);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,40 @@
|
|||
using BotSharp.Abstraction.Http;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using System.Net.Http.Headers;
|
||||
|
||||
namespace BotSharp.Plugin.HttpHandler.Hooks;
|
||||
|
||||
public class BasicHttpRequestHook : IHttpRequestHook
|
||||
{
|
||||
private readonly IServiceProvider _services;
|
||||
private readonly IHttpContextAccessor _context;
|
||||
|
||||
private const string AUTHORIZATION = "Authorization";
|
||||
private const string ORIGIN = "Origin";
|
||||
|
||||
public BasicHttpRequestHook(
|
||||
IServiceProvider services,
|
||||
IHttpContextAccessor context)
|
||||
{
|
||||
_services = services;
|
||||
_context = context;
|
||||
}
|
||||
|
||||
public void OnAddHttpHeaders(HttpHeaders headers)
|
||||
{
|
||||
var settings = _services.GetRequiredService<HttpHandlerSettings>();
|
||||
|
||||
var auth = $"{_context.HttpContext.Request.Headers[AUTHORIZATION]}";
|
||||
if (!string.IsNullOrEmpty(auth))
|
||||
{
|
||||
headers.Add(AUTHORIZATION, auth);
|
||||
}
|
||||
|
||||
var origin = $"{_context.HttpContext.Request.Headers[ORIGIN]}";
|
||||
origin = !string.IsNullOrEmpty(settings.Origin) ? settings.Origin : origin;
|
||||
if (!string.IsNullOrEmpty(origin))
|
||||
{
|
||||
headers.Add(ORIGIN, origin);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
using BotSharp.Abstraction.Agents;
|
||||
using BotSharp.Abstraction.Http;
|
||||
using BotSharp.Abstraction.Settings;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
|
||||
|
|
@ -21,5 +22,6 @@ public class HttpHandlerPlugin : IBotSharpPlugin
|
|||
});
|
||||
|
||||
services.AddScoped<IAgentUtilityHook, HttpHandlerUtilityHook>();
|
||||
services.AddScoped<IHttpRequestHook, BasicHttpRequestHook>();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@
|
|||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="MongoDB.Driver" Version="3.0.0" />
|
||||
<PackageReference Include="MongoDB.Driver" Version="3.1.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ public class AgentDocument : MongoBase
|
|||
public List<AgentKnowledgeBaseMongoElement> KnowledgeBases { get; set; }
|
||||
public List<string> Profiles { get; set; }
|
||||
public List<RoutingRuleMongoElement> RoutingRules { get; set; }
|
||||
public List<AgentRuleMongoElement> Rules { get; set; }
|
||||
public AgentLlmConfigMongoElement? LlmConfig { get; set; }
|
||||
|
||||
public DateTime CreatedTime { get; set; }
|
||||
|
|
|
|||
|
|
@ -0,0 +1,33 @@
|
|||
using BotSharp.Abstraction.Agents.Models;
|
||||
|
||||
namespace BotSharp.Plugin.MongoStorage.Models;
|
||||
|
||||
public class AgentRuleMongoElement
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public bool Disabled { get; set; }
|
||||
public string EventName { get; set; }
|
||||
public string EntityType { get; set; }
|
||||
|
||||
public static AgentRuleMongoElement ToMongoElement(AgentRule rule)
|
||||
{
|
||||
return new AgentRuleMongoElement
|
||||
{
|
||||
Name = rule.Name,
|
||||
Disabled = rule.Disabled,
|
||||
EventName = rule.EventName,
|
||||
EntityType = rule.EntityType
|
||||
};
|
||||
}
|
||||
|
||||
public static AgentRule ToDomainElement(AgentRuleMongoElement rule)
|
||||
{
|
||||
return new AgentRule
|
||||
{
|
||||
Name = rule.Name,
|
||||
Disabled = rule.Disabled,
|
||||
EventName = rule.EventName,
|
||||
EntityType = rule.EntityType
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -36,7 +36,7 @@ public class MongoDbContext
|
|||
Key = x.Split("=")[0],
|
||||
Value = x.Split("=")[1]
|
||||
}).ToList();
|
||||
|
||||
|
||||
var source = queries.FirstOrDefault(x => x.Key.IsEqualTo(DB_NAME_INDEX));
|
||||
if (source != null)
|
||||
{
|
||||
|
|
@ -168,4 +168,5 @@ public class MongoDbContext
|
|||
|
||||
public IMongoCollection<CrontabItemDocument> CrontabItems
|
||||
=> Database.GetCollection<CrontabItemDocument>($"{_collectionPrefix}_CronTabItems");
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -61,6 +61,9 @@ public partial class MongoRepository
|
|||
case AgentField.KnowledgeBase:
|
||||
UpdateAgentKnowledgeBases(agent.Id, agent.KnowledgeBases);
|
||||
break;
|
||||
case AgentField.Rule:
|
||||
UpdateAgentRules(agent.Id, agent.Rules);
|
||||
break;
|
||||
case AgentField.MaxMessageCount:
|
||||
UpdateAgentMaxMessageCount(agent.Id, agent.MaxMessageCount);
|
||||
break;
|
||||
|
|
@ -256,6 +259,20 @@ public partial class MongoRepository
|
|||
_dc.Agents.UpdateOne(filter, update);
|
||||
}
|
||||
|
||||
private void UpdateAgentRules(string agentId, List<AgentRule> rules)
|
||||
{
|
||||
if (rules == null) return;
|
||||
|
||||
var elements = rules?.Select(x => AgentRuleMongoElement.ToMongoElement(x))?.ToList() ?? [];
|
||||
|
||||
var filter = Builders<AgentDocument>.Filter.Eq(x => x.Id, agentId);
|
||||
var update = Builders<AgentDocument>.Update
|
||||
.Set(x => x.Rules, elements)
|
||||
.Set(x => x.UpdatedTime, DateTime.UtcNow);
|
||||
|
||||
_dc.Agents.UpdateOne(filter, update);
|
||||
}
|
||||
|
||||
private void UpdateAgentLlmConfig(string agentId, AgentLlmConfig? config)
|
||||
{
|
||||
var llmConfig = AgentLlmConfigMongoElement.ToMongoElement(config);
|
||||
|
|
@ -297,12 +314,12 @@ public partial class MongoRepository
|
|||
.Set(x => x.Samples, agent.Samples)
|
||||
.Set(x => x.Utilities, agent.Utilities.Select(u => AgentUtilityMongoElement.ToMongoElement(u)).ToList())
|
||||
.Set(x => x.KnowledgeBases, agent.KnowledgeBases.Select(u => AgentKnowledgeBaseMongoElement.ToMongoElement(u)).ToList())
|
||||
.Set(x => x.Rules, agent.Rules.Select(e => AgentRuleMongoElement.ToMongoElement(e)).ToList())
|
||||
.Set(x => x.LlmConfig, AgentLlmConfigMongoElement.ToMongoElement(agent.LlmConfig))
|
||||
.Set(x => x.IsPublic, agent.IsPublic)
|
||||
.Set(x => x.UpdatedTime, DateTime.UtcNow);
|
||||
|
||||
var res = _dc.Agents.UpdateOne(filter, update);
|
||||
Console.WriteLine();
|
||||
}
|
||||
#endregion
|
||||
|
||||
|
|
@ -455,6 +472,7 @@ public partial class MongoRepository
|
|||
RoutingRules = x.RoutingRules?.Select(r => RoutingRuleMongoElement.ToMongoElement(r))?.ToList() ?? [],
|
||||
Utilities = x.Utilities?.Select(u => AgentUtilityMongoElement.ToMongoElement(u))?.ToList() ?? [],
|
||||
KnowledgeBases = x.KnowledgeBases?.Select(k => AgentKnowledgeBaseMongoElement.ToMongoElement(k))?.ToList() ?? [],
|
||||
Rules = x.Rules?.Select(e => AgentRuleMongoElement.ToMongoElement(e))?.ToList() ?? [],
|
||||
CreatedTime = x.CreatedDateTime,
|
||||
UpdatedTime = x.UpdatedDateTime
|
||||
}).ToList();
|
||||
|
|
@ -546,7 +564,8 @@ public partial class MongoRepository
|
|||
Responses = agentDoc.Responses?.Select(r => AgentResponseMongoElement.ToDomainElement(r))?.ToList() ?? [],
|
||||
RoutingRules = agentDoc.RoutingRules?.Select(r => RoutingRuleMongoElement.ToDomainElement(agentDoc.Id, agentDoc.Name, r))?.ToList() ?? [],
|
||||
Utilities = agentDoc.Utilities?.Select(u => AgentUtilityMongoElement.ToDomainElement(u))?.ToList() ?? [],
|
||||
KnowledgeBases = agentDoc.KnowledgeBases?.Select(x => AgentKnowledgeBaseMongoElement.ToDomainElement(x))?.ToList() ?? []
|
||||
KnowledgeBases = agentDoc.KnowledgeBases?.Select(x => AgentKnowledgeBaseMongoElement.ToDomainElement(x))?.ToList() ?? [],
|
||||
Rules = agentDoc.Rules?.Select(e => AgentRuleMongoElement.ToDomainElement(e))?.ToList() ?? []
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -112,6 +112,14 @@ public partial class MongoRepository
|
|||
_dc.Users.UpdateOne(filter, update);
|
||||
}
|
||||
|
||||
public void UpdateUserName(string userId, string userName)
|
||||
{
|
||||
var filter = Builders<UserDocument>.Filter.Eq(x => x.Id, userId);
|
||||
var update = Builders<UserDocument>.Update
|
||||
.Set(x => x.UserName, userName);
|
||||
_dc.Users.UpdateOne(filter, update);
|
||||
}
|
||||
|
||||
public void UpdateUserVerified(string userId)
|
||||
{
|
||||
var filter = Builders<UserDocument>.Filter.Eq(x => x.Id, userId);
|
||||
|
|
@ -151,7 +159,7 @@ public partial class MongoRepository
|
|||
var update = Builders<UserDocument>.Update.Set(x => x.Phone, phone)
|
||||
.Set(x => x.UpdatedTime, DateTime.UtcNow)
|
||||
.Set(x => x.RegionCode, regionCode)
|
||||
.Set(x => x.UserName, phone)
|
||||
//.Set(x => x.UserName, phone)
|
||||
.Set(x => x.FirstName, phone);
|
||||
_dc.Users.UpdateOne(filter, update);
|
||||
}
|
||||
|
|
@ -331,10 +339,10 @@ public partial class MongoRepository
|
|||
.FirstOrDefault(x => x.Id == userId || (x.ExternalId != null && x.ExternalId == userId));
|
||||
if (user == null) return;
|
||||
var curDash = user.Dashboard ?? new Dashboard();
|
||||
curDash.ConversationList.Add(new DashboardConversation
|
||||
{
|
||||
curDash.ConversationList.Add(new DashboardConversation
|
||||
{
|
||||
Id = Guid.NewGuid().ToString(),
|
||||
ConversationId = conversationId
|
||||
ConversationId = conversationId
|
||||
});
|
||||
|
||||
var filter = Builders<UserDocument>.Filter.Eq(x => x.Id, userId);
|
||||
|
|
|
|||
|
|
@ -9,8 +9,9 @@ public class ProviderHelper
|
|||
{
|
||||
var settingsService = services.GetRequiredService<ILlmProviderService>();
|
||||
var settings = settingsService.GetSetting(provider, model);
|
||||
var client = new OpenAIClient(new ApiKeyCredential(settings.ApiKey));
|
||||
return client;
|
||||
var options = !string.IsNullOrEmpty(settings.Endpoint) ?
|
||||
new OpenAIClientOptions { Endpoint = new Uri(settings.Endpoint) } : null;
|
||||
return new OpenAIClient(new ApiKeyCredential(settings.ApiKey), options);
|
||||
}
|
||||
|
||||
public static List<RoleDialogModel> GetChatSamples(List<string> lines)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,22 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>$(TargetFramework)</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<LangVersion>$(LangVersion)</LangVersion>
|
||||
<VersionPrefix>$(BotSharpVersion)</VersionPrefix>
|
||||
<GeneratePackageOnBuild>$(GeneratePackageOnBuild)</GeneratePackageOnBuild>
|
||||
<GenerateDocumentationFile>$(GenerateDocumentationFile)</GenerateDocumentationFile>
|
||||
<OutputPath>$(SolutionDir)packages</OutputPath>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Selenium.WebDriver" Version="4.27.0" />
|
||||
<PackageReference Include="HtmlAgilityPack" Version="1.11.71" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\Infrastructure\BotSharp.Core\BotSharp.Core.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
using OpenQA.Selenium.Chrome;
|
||||
using System.IO;
|
||||
|
||||
namespace BotSharp.Plugin.WebDriver.Drivers.SeleniumDriver;
|
||||
namespace BotSharp.Plugin.Selenium.Drivers;
|
||||
|
||||
public class SeleniumInstance : IDisposable
|
||||
{
|
||||
|
|
@ -1,7 +1,6 @@
|
|||
using OpenQA.Selenium;
|
||||
using OpenQA.Selenium.Interactions;
|
||||
|
||||
namespace BotSharp.Plugin.WebDriver.Drivers.SeleniumDriver;
|
||||
namespace BotSharp.Plugin.Selenium.Drivers;
|
||||
|
||||
public partial class SeleniumWebDriver
|
||||
{
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
namespace BotSharp.Plugin.WebDriver.Drivers.SeleniumDriver;
|
||||
namespace BotSharp.Plugin.Selenium.Drivers;
|
||||
|
||||
public partial class SeleniumWebDriver
|
||||
{
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
namespace BotSharp.Plugin.WebDriver.Drivers.SeleniumDriver;
|
||||
namespace BotSharp.Plugin.Selenium.Drivers;
|
||||
|
||||
public partial class SeleniumWebDriver
|
||||
{
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
namespace BotSharp.Plugin.WebDriver.Drivers.SeleniumDriver;
|
||||
namespace BotSharp.Plugin.Selenium.Drivers;
|
||||
|
||||
public partial class SeleniumWebDriver
|
||||
{
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
using System.Net.Http;
|
||||
|
||||
namespace BotSharp.Plugin.WebDriver.Drivers.SeleniumDriver;
|
||||
namespace BotSharp.Plugin.Selenium.Drivers;
|
||||
|
||||
public partial class SeleniumWebDriver
|
||||
{
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
namespace BotSharp.Plugin.WebDriver.Drivers.SeleniumDriver;
|
||||
namespace BotSharp.Plugin.Selenium.Drivers;
|
||||
|
||||
public partial class SeleniumWebDriver
|
||||
{
|
||||
|
|
@ -1,7 +1,6 @@
|
|||
using OpenQA.Selenium;
|
||||
using System.Collections.ObjectModel;
|
||||
|
||||
namespace BotSharp.Plugin.WebDriver.Drivers.SeleniumDriver;
|
||||
namespace BotSharp.Plugin.Selenium.Drivers;
|
||||
|
||||
public partial class SeleniumWebDriver
|
||||
{
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
namespace BotSharp.Plugin.WebDriver.Drivers.SeleniumDriver;
|
||||
namespace BotSharp.Plugin.Selenium.Drivers;
|
||||
|
||||
public partial class SeleniumWebDriver : IWebBrowser
|
||||
{
|
||||
|
|
@ -86,4 +86,9 @@ public partial class SeleniumWebDriver : IWebBrowser
|
|||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public Task PressKey(MessageInfo message, string key)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
23
src/Plugins/BotSharp.Plugin.Selenium/Using.cs
Normal file
23
src/Plugins/BotSharp.Plugin.Selenium/Using.cs
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
global using System;
|
||||
global using System.Collections.Generic;
|
||||
global using System.Text;
|
||||
global using System.Threading.Tasks;
|
||||
global using System.Text.Json;
|
||||
global using System.Linq;
|
||||
global using System.Text.RegularExpressions;
|
||||
global using Microsoft.Extensions.Configuration;
|
||||
global using Microsoft.Extensions.DependencyInjection;
|
||||
global using Microsoft.Extensions.Logging;
|
||||
global using BotSharp.Abstraction.Browsing.Enums;
|
||||
global using BotSharp.Abstraction.Conversations;
|
||||
global using BotSharp.Abstraction.Plugins;
|
||||
global using BotSharp.Abstraction.Conversations.Models;
|
||||
global using BotSharp.Abstraction.Functions;
|
||||
global using BotSharp.Abstraction.Agents.Models;
|
||||
global using BotSharp.Abstraction.Templating;
|
||||
global using BotSharp.Abstraction.Agents;
|
||||
global using BotSharp.Abstraction.Utilities;
|
||||
global using BotSharp.Abstraction.Browsing.Models;
|
||||
global using BotSharp.Abstraction.Browsing;
|
||||
|
||||
global using OpenQA.Selenium;
|
||||
|
|
@ -1,13 +1,9 @@
|
|||
using BotSharp.Abstraction.Agents.Enums;
|
||||
using BotSharp.Abstraction.Messaging.Enums;
|
||||
using BotSharp.Abstraction.Messaging.Models.RichContent.Template;
|
||||
using BotSharp.Abstraction.Messaging.Models.RichContent;
|
||||
using BotSharp.Abstraction.Messaging;
|
||||
using BotSharp.Abstraction.Planning;
|
||||
using BotSharp.Abstraction.Routing;
|
||||
using BotSharp.Core.Infrastructures;
|
||||
using System.Text.RegularExpressions;
|
||||
using BotSharp.Plugin.SqlDriver.Interfaces;
|
||||
|
||||
namespace BotSharp.Plugin.SqlDriver.Hooks;
|
||||
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
<PropertyGroup>
|
||||
<TargetFramework>$(TargetFramework)</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<LangVersion>12.0</LangVersion>
|
||||
<LangVersion>$(LangVersion)</LangVersion>
|
||||
<VersionPrefix>$(BotSharpVersion)</VersionPrefix>
|
||||
<GeneratePackageOnBuild>$(GeneratePackageOnBuild)</GeneratePackageOnBuild>
|
||||
<GenerateDocumentationFile>$(GenerateDocumentationFile)</GenerateDocumentationFile>
|
||||
|
|
@ -11,16 +11,7 @@
|
|||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Remove="Drivers\SeleniumDriver\**" />
|
||||
<Compile Remove="packages\**" />
|
||||
<EmbeddedResource Remove="Drivers\SeleniumDriver\**" />
|
||||
<EmbeddedResource Remove="packages\**" />
|
||||
<None Remove="Drivers\SeleniumDriver\**" />
|
||||
<None Remove="packages\**" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Playwright" Version="1.48.0" />
|
||||
<PackageReference Include="Microsoft.Playwright" Version="1.49.0" />
|
||||
<PackageReference Include="HtmlAgilityPack" Version="1.11.71" />
|
||||
</ItemGroup>
|
||||
|
||||
|
|
|
|||
|
|
@ -45,28 +45,36 @@ public class PlaywrightInstance : IDisposable
|
|||
_playwright = await Playwright.CreateAsync();
|
||||
}
|
||||
|
||||
string tempFolderPath = $"{Path.GetTempPath()}\\playwright\\{ctxId}";
|
||||
|
||||
_contexts[ctxId] = await _playwright.Chromium.LaunchPersistentContextAsync(tempFolderPath, new BrowserTypeLaunchPersistentContextOptions
|
||||
if (!string.IsNullOrEmpty(args.RemoteHostUrl))
|
||||
{
|
||||
Headless = args.Headless,
|
||||
Channel = "chrome",
|
||||
ViewportSize = new ViewportSize
|
||||
var browser = await _playwright.Chromium.ConnectOverCDPAsync(args.RemoteHostUrl);
|
||||
_contexts[ctxId] = browser.Contexts[0];
|
||||
}
|
||||
else
|
||||
{
|
||||
string userDataDir = args.UserDataDir ?? $"{Path.GetTempPath()}\\playwright\\{ctxId}";
|
||||
_contexts[ctxId] = await _playwright.Chromium.LaunchPersistentContextAsync(userDataDir, new BrowserTypeLaunchPersistentContextOptions
|
||||
{
|
||||
Width = 1600,
|
||||
Height = 900
|
||||
},
|
||||
IgnoreDefaultArgs =
|
||||
[
|
||||
"--enable-automation",
|
||||
],
|
||||
Args =
|
||||
[
|
||||
"--disable-infobars",
|
||||
"--test-type"
|
||||
// "--start-maximized"
|
||||
]
|
||||
});
|
||||
Headless = args.Headless,
|
||||
Channel = "chrome",
|
||||
ViewportSize = new ViewportSize
|
||||
{
|
||||
Width = 1600,
|
||||
Height = 900
|
||||
},
|
||||
IgnoreDefaultArgs =
|
||||
[
|
||||
"--enable-automation",
|
||||
],
|
||||
Args =
|
||||
[
|
||||
"--disable-infobars",
|
||||
"--test-type"
|
||||
// "--start-maximized"
|
||||
]
|
||||
});
|
||||
}
|
||||
|
||||
_pages[ctxId] = new List<IPage>();
|
||||
|
||||
_contexts[ctxId].Page += async (sender, page) =>
|
||||
|
|
@ -100,12 +108,7 @@ public class PlaywrightInstance : IDisposable
|
|||
return _contexts[ctxId];
|
||||
}
|
||||
|
||||
public async Task<IPage> NewPage(MessageInfo message,
|
||||
bool enableResponseCallback = false,
|
||||
bool responseInMemory = false,
|
||||
List<WebPageResponseData>? responseContainer = null,
|
||||
string[]? excludeResponseUrls = null,
|
||||
string[]? includeResponseUrls = null)
|
||||
public async Task<IPage> NewPage(MessageInfo message, PageActionArgs args)
|
||||
{
|
||||
var context = await GetContext(message.ContextId);
|
||||
var page = await context.NewPageAsync();
|
||||
|
|
@ -115,70 +118,78 @@ public class PlaywrightInstance : IDisposable
|
|||
var js = @"Object.defineProperties(navigator, {webdriver:{get:()=>false}});";
|
||||
await page.AddInitScriptAsync(js);
|
||||
|
||||
if (!enableResponseCallback)
|
||||
if (!args.EnableResponseCallback)
|
||||
{
|
||||
return page;
|
||||
}
|
||||
|
||||
page.Response += async (sender, e) =>
|
||||
{
|
||||
if (e.Status != 204 &&
|
||||
e.Headers.ContainsKey("content-type") &&
|
||||
(e.Request.ResourceType == "fetch" || e.Request.ResourceType == "xhr") &&
|
||||
(excludeResponseUrls == null || !excludeResponseUrls.Any(url => e.Url.ToLower().Contains(url))) &&
|
||||
(includeResponseUrls == null || includeResponseUrls.Any(url => e.Url.ToLower().Contains(url))))
|
||||
{
|
||||
Serilog.Log.Information($"{e.Request.Method}: {e.Url}");
|
||||
|
||||
try
|
||||
{
|
||||
var result = new WebPageResponseData
|
||||
{
|
||||
Url = e.Url.ToLower(),
|
||||
PostData = e.Request?.PostData ?? string.Empty,
|
||||
ResponseInMemory = responseInMemory
|
||||
};
|
||||
|
||||
if (e.Headers["content-type"].Contains("application/json"))
|
||||
{
|
||||
if (e.Status == 200 && e.Ok)
|
||||
{
|
||||
var json = await e.JsonAsync();
|
||||
result.ResponseData = JsonSerializer.Serialize(json);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var html = await e.TextAsync();
|
||||
result.ResponseData = html;
|
||||
}
|
||||
|
||||
if (responseContainer != null && responseInMemory)
|
||||
{
|
||||
responseContainer.Add(result);
|
||||
}
|
||||
|
||||
Serilog.Log.Warning($"Response status: {e.Status} {e.StatusText}, OK: {e.Ok}");
|
||||
var webPageResponseHooks = _services.GetServices<IWebPageResponseHook>();
|
||||
foreach (var hook in webPageResponseHooks)
|
||||
{
|
||||
hook.OnDataFetched(message, result);
|
||||
}
|
||||
}
|
||||
catch (ObjectDisposedException ex)
|
||||
{
|
||||
Serilog.Log.Information(ex.Message);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Serilog.Log.Error($"{e.Url}\r\n" + ex.ToString());
|
||||
}
|
||||
}
|
||||
await HandleFetchResponse(e, message, args);
|
||||
};
|
||||
|
||||
return page;
|
||||
}
|
||||
|
||||
public async Task HandleFetchResponse(IResponse response, MessageInfo message, PageActionArgs args)
|
||||
{
|
||||
if (response.Status != 204 &&
|
||||
response.Headers.ContainsKey("content-type") &&
|
||||
(response.Request.ResourceType == "fetch" || response.Request.ResourceType == "xhr") &&
|
||||
(args.ExcludeResponseUrls == null || !args.ExcludeResponseUrls.Any(url => response.Url.ToLower().Contains(url))) &&
|
||||
(args.IncludeResponseUrls == null || args.IncludeResponseUrls.Any(url => response.Url.ToLower().Contains(url))))
|
||||
{
|
||||
Serilog.Log.Information($"{response.Request.Method}: {response.Url}");
|
||||
|
||||
try
|
||||
{
|
||||
var result = new WebPageResponseData
|
||||
{
|
||||
Url = response.Url.ToLower(),
|
||||
PostData = response.Request?.PostData ?? string.Empty,
|
||||
ResponseInMemory = args.ResponseInMemory
|
||||
};
|
||||
|
||||
var html = await response.TextAsync();
|
||||
if (response.Headers["content-type"].Contains("application/json"))
|
||||
{
|
||||
if (response.Status == 200 && response.Ok)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(html))
|
||||
{
|
||||
var json = await response.JsonAsync();
|
||||
result.ResponseData = JsonSerializer.Serialize(json);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
result.ResponseData = html;
|
||||
}
|
||||
|
||||
if (args.ResponseContainer != null && args.ResponseInMemory)
|
||||
{
|
||||
args.ResponseContainer.Add(result);
|
||||
}
|
||||
|
||||
Serilog.Log.Warning($"Response status: {response.Status} {response.StatusText}, OK: {response.Ok}");
|
||||
var webPageResponseHooks = _services.GetServices<IWebPageResponseHook>();
|
||||
foreach (var hook in webPageResponseHooks)
|
||||
{
|
||||
hook.OnDataFetched(message, result);
|
||||
}
|
||||
}
|
||||
catch (ObjectDisposedException ex)
|
||||
{
|
||||
Serilog.Log.Information(ex.Message);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Serilog.Log.Error($"{response.Url}\r\n" + ex.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Wait page and network until timeout in seconds
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ public partial class PlaywrightWebDriver
|
|||
if (result.IsSuccess)
|
||||
{
|
||||
await DoAction(message, action, result);
|
||||
result.UrlAfterAction = _instance.GetPage(message.ContextId)?.Url;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -113,6 +113,7 @@ public partial class PlaywrightWebDriver
|
|||
}
|
||||
|
||||
// Release mouse button
|
||||
await Task.Delay(1000);
|
||||
await mouse.UpAsync();
|
||||
}
|
||||
else
|
||||
|
|
|
|||
|
|
@ -19,4 +19,25 @@ public partial class PlaywrightWebDriver
|
|||
Body = value ?? string.Empty
|
||||
};
|
||||
}
|
||||
|
||||
public async Task<BrowserActionResult> SetAttributeValue(MessageInfo message, ElementLocatingArgs location)
|
||||
{
|
||||
var page = _instance.GetPage(message.ContextId);
|
||||
ILocator locator = page.Locator(location.Selector);
|
||||
var elementCount = await locator.CountAsync();
|
||||
|
||||
if (elementCount > 0)
|
||||
{
|
||||
foreach (var element in await locator.AllAsync())
|
||||
{
|
||||
var script = $"element => element.{location.AttributeName} = '{location.AttributeValue}'";
|
||||
var result = await locator.EvaluateAsync(script);
|
||||
}
|
||||
}
|
||||
|
||||
return new BrowserActionResult
|
||||
{
|
||||
IsSuccess = true
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,32 +8,41 @@ public partial class PlaywrightWebDriver
|
|||
var context = await _instance.GetContext(message.ContextId);
|
||||
try
|
||||
{
|
||||
var page = await _instance.NewPage(message, enableResponseCallback: args.EnableResponseCallback,
|
||||
responseInMemory: args.ResponseInMemory,
|
||||
responseContainer: args.ResponseContainer,
|
||||
excludeResponseUrls: args.ExcludeResponseUrls,
|
||||
includeResponseUrls: args.IncludeResponseUrls);
|
||||
|
||||
Serilog.Log.Information($"goto page: {args.Url}");
|
||||
|
||||
if (args.OpenNewTab && page != null && page.Url == "about:blank")
|
||||
IPage? page = null;
|
||||
if (!args.OpenNewTab)
|
||||
{
|
||||
page = await _instance.NewPage(message,
|
||||
enableResponseCallback: args.EnableResponseCallback,
|
||||
responseInMemory: args.ResponseInMemory,
|
||||
responseContainer: args.ResponseContainer,
|
||||
excludeResponseUrls: args.ExcludeResponseUrls,
|
||||
includeResponseUrls: args.IncludeResponseUrls);
|
||||
page = _instance.Contexts[message.ContextId].Pages.LastOrDefault();
|
||||
|
||||
if (page != null)
|
||||
{
|
||||
await page.EvaluateAsync(@"() => {
|
||||
window.open('', '_blank');
|
||||
}");
|
||||
|
||||
if (args.EnableResponseCallback)
|
||||
{
|
||||
page.Response += async (sender, e) =>
|
||||
{
|
||||
await _instance.HandleFetchResponse(e, message, args);
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
page = await _instance.NewPage(message, args);
|
||||
|
||||
Serilog.Log.Information($"goto page: {args.Url}");
|
||||
|
||||
if (args.OpenNewTab && page != null && page.Url == "about:blank")
|
||||
{
|
||||
page = await _instance.NewPage(message, args);
|
||||
}
|
||||
}
|
||||
|
||||
if (page == null)
|
||||
{
|
||||
page = await _instance.NewPage(message,
|
||||
enableResponseCallback: args.EnableResponseCallback,
|
||||
responseInMemory: args.ResponseInMemory,
|
||||
responseContainer: args.ResponseContainer,
|
||||
excludeResponseUrls: args.ExcludeResponseUrls,
|
||||
includeResponseUrls: args.IncludeResponseUrls);
|
||||
page = await _instance.NewPage(message, args);
|
||||
}
|
||||
|
||||
// Active current tab
|
||||
|
|
|
|||
|
|
@ -14,6 +14,13 @@ public partial class PlaywrightWebDriver
|
|||
{
|
||||
var result = new BrowserActionResult();
|
||||
var page = _instance.GetPage(message.ContextId);
|
||||
if (page == null)
|
||||
{
|
||||
return new BrowserActionResult
|
||||
{
|
||||
IsSuccess = false
|
||||
};
|
||||
}
|
||||
ILocator locator = page.Locator("body");
|
||||
int count = 0;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
using SQLitePCL;
|
||||
|
||||
namespace BotSharp.Plugin.WebDriver.Functions;
|
||||
|
||||
public class OpenBrowserFn : IFunctionCallback
|
||||
|
|
|
|||
|
|
@ -1,10 +1,25 @@
|
|||
using HtmlAgilityPack;
|
||||
using System.Net.Http;
|
||||
|
||||
namespace BotSharp.Plugin.WebDriver;
|
||||
|
||||
public static class WebPageHelper
|
||||
{
|
||||
public static HtmlNode GetElement(string html, string selector)
|
||||
{
|
||||
var htmlDoc = new HtmlDocument();
|
||||
htmlDoc.LoadHtml(html);
|
||||
|
||||
return htmlDoc.DocumentNode.SelectSingleNode(selector);
|
||||
}
|
||||
|
||||
public static HtmlNodeCollection GetElements(string html, string selector)
|
||||
{
|
||||
var htmlDoc = new HtmlDocument();
|
||||
htmlDoc.LoadHtml(html);
|
||||
|
||||
return htmlDoc.DocumentNode.SelectNodes(selector);
|
||||
}
|
||||
|
||||
public static string RemoveElements(string html, string selector)
|
||||
{
|
||||
var htmlDoc = new HtmlDocument();
|
||||
|
|
|
|||
Loading…
Reference in a new issue