Merge pull request #286 from SciSharp/master

merge latest code
This commit is contained in:
geffzhang 2024-02-02 15:05:08 +08:00 committed by GitHub
commit 4d5ebd8bb0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
48 changed files with 481 additions and 223 deletions

View file

@ -18,6 +18,7 @@ public interface IBotSharpRepository
#region User
User? GetUserByEmail(string email);
User? GetUserById(string id);
User? GetUserByUserName(string userName);
void CreateUser(User user);
#endregion

View file

@ -0,0 +1,10 @@
using BotSharp.Abstraction.Users.Models;
using System.Security.Claims;
namespace BotSharp.Abstraction.Users;
public interface IAuthenticationHook
{
Task<User> Authenticate(string id, string password);
void AddClaims(List<Claim> claims);
}

View file

@ -7,4 +7,5 @@ public interface IUserIdentity
string UserName { get; }
string FirstName { get; }
string LastName { get; }
string FullName { get; }
}

View file

@ -11,6 +11,7 @@ public class User
public string Email { get; set; } = string.Empty;
public string Salt { get; set; } = string.Empty;
public string Password { get; set; } = string.Empty;
public string Source { get; set; } = "internal";
public string? ExternalId { get; set; }
public string Role { get; set; } = UserRole.Client;
public DateTime UpdatedTime { get; set; } = DateTime.UtcNow;

View file

@ -36,9 +36,9 @@ public class AgentPlugin : IBotSharpPlugin
{
SubMenu = new List<PluginMenuDef>
{
new PluginMenuDef("Router", link: "/page/agent/router"), // icon: "bx bx-map-pin"
new PluginMenuDef("Evaluator", link: "/page/agent/evaluator"), // icon: "bx bx-task"
new PluginMenuDef("Agents", link: "/page/agent"), // icon: "bx bx-bot"
new PluginMenuDef("Router", link: "page/agent/router"), // icon: "bx bx-map-pin"
new PluginMenuDef("Evaluator", link: "page/agent/evaluator"), // icon: "bx bx-task"
new PluginMenuDef("Agents", link: "page/agent"), // icon: "bx bx-bot"
}
});

View file

@ -119,6 +119,7 @@
<PackageReference Include="Colorful.Console" Version="1.2.15" />
<PackageReference Include="EntityFrameworkCore.BootKit" Version="6.2.1" />
<PackageReference Include="Fluid.Core" Version="2.5.0" />
<PackageReference Include="Nanoid" Version="3.0.0" />
</ItemGroup>
<ItemGroup>

View file

@ -51,7 +51,7 @@ public class ConversationPlugin : IBotSharpPlugin
public bool AttachMenu(List<PluginMenuDef> menu)
{
var section = menu.First(x => x.Label == "Apps");
menu.Add(new PluginMenuDef("Conversation", link: "/page/conversation", icon: "bx bx-conversation", weight: section.Weight + 5));
menu.Add(new PluginMenuDef("Conversation", link: "page/conversation", icon: "bx bx-conversation", weight: section.Weight + 5));
return true;
}
}

View file

@ -192,20 +192,17 @@ public class BotSharpDbContext : Database, IBotSharpRepository
#endregion
#region User
public User? GetUserByEmail(string email)
{
throw new NotImplementedException();
}
public User? GetUserByEmail(string email)
=> throw new NotImplementedException();
public User? GetUserById(string id)
{
throw new NotImplementedException();
}
public User? GetUserById(string id)
=> throw new NotImplementedException();
public void CreateUser(User user)
{
throw new NotImplementedException();
}
public User? GetUserByUserName(string userName)
=> throw new NotImplementedException();
public void CreateUser(User user)
=> throw new NotImplementedException();
#endregion

View file

@ -1,31 +1,35 @@
using BotSharp.Abstraction.Users.Models;
using System.IO;
namespace BotSharp.Core.Repository
namespace BotSharp.Core.Repository;
public partial class FileRepository
{
public partial class FileRepository
public User? GetUserByEmail(string email)
{
public User? GetUserByEmail(string email)
{
return Users.FirstOrDefault(x => x.Email == email);
}
return Users.FirstOrDefault(x => x.Email == email.ToLower());
}
public User? GetUserById(string id = null)
{
return Users.FirstOrDefault(x => x.ExternalId == id || x.Id == id);
}
public User? GetUserById(string id = null)
{
return Users.FirstOrDefault(x => x.Id == id || (x.ExternalId != null && x.ExternalId == id));
}
public void CreateUser(User user)
public User? GetUserByUserName(string userName = null)
{
return Users.FirstOrDefault(x => x.UserName == userName.ToLower());
}
public void CreateUser(User user)
{
var userId = Guid.NewGuid().ToString();
user.Id = userId;
var dir = Path.Combine(_dbSettings.FileRepository, "users", userId);
if (!Directory.Exists(dir))
{
var userId = Guid.NewGuid().ToString();
user.Id = userId;
var dir = Path.Combine(_dbSettings.FileRepository, "users", userId);
if (!Directory.Exists(dir))
{
Directory.CreateDirectory(dir);
}
var path = Path.Combine(dir, "user.json");
File.WriteAllText(path, JsonSerializer.Serialize(user, _options));
Directory.CreateDirectory(dir);
}
var path = Path.Combine(dir, "user.json");
File.WriteAllText(path, JsonSerializer.Serialize(user, _options));
}
}

View file

@ -4,11 +4,6 @@ using FunctionDef = BotSharp.Abstraction.Functions.Models.FunctionDef;
using BotSharp.Abstraction.Users.Models;
using BotSharp.Abstraction.Agents.Models;
using MongoDB.Driver;
using BotSharp.Abstraction.Routing.Models;
using BotSharp.Abstraction.Repositories.Filters;
using BotSharp.Abstraction.Repositories.Models;
using BotSharp.Abstraction.Routing.Settings;
using BotSharp.Abstraction.Evaluations.Settings;
using System.Text.Encodings.Web;
using BotSharp.Abstraction.Plugins.Models;
using BotSharp.Abstraction.Statistics.Settings;

View file

@ -28,4 +28,7 @@ public class UserIdentity : IUserIdentity
public string LastName
=> _claims?.FirstOrDefault(x => x.Type == ClaimTypes.Surname)?.Value!;
public string FullName
=> $"{FirstName} {LastName}".Trim();
}

View file

@ -2,6 +2,7 @@ using BotSharp.Abstraction.Repositories;
using BotSharp.Abstraction.Users.Models;
using Microsoft.Extensions.Configuration;
using Microsoft.IdentityModel.Tokens;
using NanoidDotNet;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
@ -11,24 +12,42 @@ public class UserService : IUserService
{
private readonly IServiceProvider _services;
private readonly IUserIdentity _user;
private readonly ILogger _logger;
public UserService(IServiceProvider services, IUserIdentity user)
public UserService(IServiceProvider services, IUserIdentity user, ILogger<UserService> logger)
{
_services = services;
_user = user;
_logger = logger;
}
public async Task<User> CreateUser(User user)
{
if (string.IsNullOrEmpty(user.UserName))
{
// generate unique name
var name = user.Email.Split("@").First() + "-" + Nanoid.Generate("0123456789botsharp", 6);
user.UserName = name;
}
else
{
user.UserName = user.UserName.ToLower();
}
var db = _services.GetRequiredService<IBotSharpRepository>();
var record = db.GetUserByEmail(user.Email);
var record = db.GetUserByUserName(user.UserName);
if (record != null)
{
return record;
}
if (string.IsNullOrEmpty(user.Id))
{
user.Id = Guid.NewGuid().ToString();
}
record = user;
record.UserName = user.UserName.ToLower();
record.Email = user.Email.ToLower();
record.Salt = Guid.NewGuid().ToString("N");
record.Password = Utilities.HashText(user.Password, record.Salt);
@ -43,10 +62,52 @@ public class UserService : IUserService
public async Task<Token> GetToken(string authorization)
{
var base64 = Encoding.UTF8.GetString(Convert.FromBase64String(authorization));
var (userEmail, password) = base64.SplitAsTuple(":");
var (id, password) = base64.SplitAsTuple(":");
var db = _services.GetRequiredService<IBotSharpRepository>();
var record = db.GetUserByEmail(userEmail);
var record = id.Contains("@") ? db.GetUserByEmail(id) : db.GetUserByUserName(id);
if (record == null)
{
record = db.GetUserByUserName(id);
}
if (record == null || record.Source != "internal")
{
// check 3rd party user
var validators = _services.GetServices<IAuthenticationHook>();
foreach (var validator in validators)
{
var user = await validator.Authenticate(id, password);
if (user == null)
{
continue;
}
if (string.IsNullOrEmpty(user.Source) || user.Source == "internal")
{
_logger.LogError($"Please set source name in the Authenticate hook.");
return null;
}
if (record == null)
{
// create a local user record
record = new User
{
UserName = user.UserName,
Email = user.Email,
FirstName = user.FirstName,
LastName = user.LastName,
Source = user.Source,
ExternalId = user.ExternalId,
Password = user.Password,
};
await CreateUser(record);
}
break;
}
}
if (record == null)
{
return default;
@ -70,33 +131,43 @@ public class UserService : IUserService
private string GenerateJwtToken(User user)
{
var claims = new List<Claim>
{
new Claim(JwtRegisteredClaimNames.NameId, user.Id),
new Claim(JwtRegisteredClaimNames.UniqueName, user.UserName),
new Claim(JwtRegisteredClaimNames.Email, user.Email),
new Claim(JwtRegisteredClaimNames.GivenName, user.FirstName),
new Claim(JwtRegisteredClaimNames.FamilyName, user.LastName),
new Claim("source", user.Source),
new Claim("external_id", user.ExternalId),
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString())
};
var validators = _services.GetServices<IAuthenticationHook>();
foreach (var validator in validators)
{
validator.AddClaims(claims);
}
var config = _services.GetRequiredService<IConfiguration>();
var issuer = config["Jwt:Issuer"];
var audience = config["Jwt:Audience"];
var key = Encoding.ASCII.GetBytes(config["Jwt:Key"]);
var tokenDescriptor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(new[]
{
new Claim(JwtRegisteredClaimNames.NameId, user.Id),
new Claim(JwtRegisteredClaimNames.Email, user.Email),
new Claim(JwtRegisteredClaimNames.GivenName, user.FirstName),
new Claim(JwtRegisteredClaimNames.FamilyName, user.LastName),
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString())
}),
Expires = DateTime.UtcNow.AddMinutes(5),
Subject = new ClaimsIdentity(claims),
Expires = DateTime.UtcNow.AddHours(2),
Issuer = issuer,
Audience = audience,
SigningCredentials = new SigningCredentials
(new SymmetricSecurityKey(key),
SecurityAlgorithms.HmacSha512Signature)
SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key),
SecurityAlgorithms.HmacSha512Signature)
};
var tokenHandler = new JwtSecurityTokenHandler();
var token = tokenHandler.CreateToken(tokenDescriptor);
return tokenHandler.WriteToken(token);
}
[MemoryCache(10 * 60)]
[MemoryCache(10 * 60, perInstanceCache: true)]
public async Task<User> GetMyProfile()
{
var db = _services.GetRequiredService<IBotSharpRepository>();
@ -109,6 +180,16 @@ public class UserService : IUserService
{
var db = _services.GetRequiredService<IBotSharpRepository>();
var user = db.GetUserById(id);
if (user == null)
{
user = new User
{
Id = id,
FirstName = "Unknown",
LastName = "Anonymous",
Role = AgentRole.User
};
}
return user;
}
}

View file

@ -1,2 +1,2 @@
In order to execute the listed instructions in the order specified by the user.
In order to sequentially execute user needs,
What is the next step based on the CONVERSATION?

View file

@ -29,7 +29,7 @@ public class AgentController : ControllerBase
{
AgentIds = new List<string> { id }
});
return agents.Items.First();
return agents.Items.FirstOrDefault();
}
[HttpGet("/agents")]

View file

@ -1,3 +1,5 @@
using BotSharp.Abstraction.Users.Models;
namespace BotSharp.OpenAPI.Controllers;
[Authorize]
@ -66,20 +68,40 @@ public class ConversationController : ControllerBase
var history = conv.GetDialogHistory();
var userService = _services.GetRequiredService<IUserService>();
var agentService = _services.GetRequiredService<IAgentService>();
var dialogs = new List<ChatResponseModel>();
foreach (var message in history)
{
var user = await userService.GetUser(message.SenderId);
dialogs.Add(new ChatResponseModel
if (message.Role == AgentRole.User)
{
ConversationId = conversationId,
MessageId = message.MessageId,
CreatedAt = message.CreatedAt,
Text = message.Content,
Sender = UserViewModel.FromUser(user)
});
var user = await userService.GetUser(message.SenderId);
dialogs.Add(new ChatResponseModel
{
ConversationId = conversationId,
MessageId = message.MessageId,
CreatedAt = message.CreatedAt,
Text = message.Content,
Sender = UserViewModel.FromUser(user)
});
}
else
{
var agent = await agentService.GetAgent(message.CurrentAgentId);
dialogs.Add(new ChatResponseModel
{
ConversationId = conversationId,
MessageId = message.MessageId,
CreatedAt = message.CreatedAt,
Text = message.Content,
Sender = new UserViewModel
{
FirstName = agent.Name,
Role = message.Role,
}
});
}
}
return dialogs;

View file

@ -36,8 +36,8 @@ public class PluginController : ControllerBase
{
IsHeader = true
},
new PluginMenuDef("Plugins", link: "/page/plugin", icon: "bx bx-plug", weight: 31),
new PluginMenuDef("Settings", link: "/page/setting", icon: "bx bx-cog", weight: 32),
new PluginMenuDef("Plugins", link: "page/plugin", icon: "bx bx-plug", weight: 31),
new PluginMenuDef("Settings", link: "page/setting", icon: "bx bx-cog", weight: 32),
};
var loader = _services.GetRequiredService<PluginLoader>();

View file

@ -29,9 +29,9 @@ public class UserViewModel
{
return new UserViewModel
{
FirstName = "AI",
LastName = "Assistant",
Role = AgentRole.Assistant
FirstName = "Unknown",
LastName = "Anonymous",
Role = AgentRole.User
};
}

View file

@ -12,7 +12,7 @@ public class DashboardPlugin : IBotSharpPlugin
public string Id => "d42a0c21-b461-44f6-ada2-499510d260af";
public string Name => "Dashboard";
public string Description => "Dashboard that offers real-time statistics on model performance, usage trends, and user feedback";
public string IconUrl => "https://cdn0.iconfinder.com/data/icons/octicons/1024/dashboard-512.png";
public void RegisterDI(IServiceCollection services, IConfiguration config)
{
services.AddScoped<IConversationHook, StatsConversationHook>();
@ -26,7 +26,7 @@ public class DashboardPlugin : IBotSharpPlugin
public bool AttachMenu(List<PluginMenuDef> menu)
{
var section = menu.First(x => x.Label == "Apps");
menu.Add(new PluginMenuDef("Dashboard", link: "/page/dashboard", icon: "bx bx-home-circle", weight: section.Weight - 1));
menu.Add(new PluginMenuDef("Dashboard", link: "page/dashboard", icon: "bx bx-home-circle", weight: section.Weight - 1));
return true;
}
}

View file

@ -1,6 +1,5 @@
using BotSharp.Abstraction.Agents;
using BotSharp.Abstraction.Agents.Enums;
using BotSharp.Abstraction.Conversations.Settings;
using BotSharp.Abstraction.Loggers;
using BotSharp.Plugin.HuggingFace.Services;
using BotSharp.Plugin.HuggingFace.Settings;

View file

@ -1,3 +1,5 @@
using BotSharp.Abstraction.Users.Models;
namespace BotSharp.Plugin.MongoStorage.Collections;
public class UserDocument : MongoBase
@ -8,9 +10,27 @@ public class UserDocument : MongoBase
public string Email { get; set; }
public string Salt { get; set; }
public string Password { get; set; }
public string Source { get; set; } = "internal";
public string? ExternalId { get; set; }
public string Role { get; set; }
public DateTime CreatedTime { get; set; }
public DateTime UpdatedTime { get; set; }
public User ToUser()
{
return new User
{
Id = Id,
UserName = UserName,
FirstName = FirstName,
LastName = LastName,
Email = Email,
Password = Password,
Salt = Salt,
Source = Source,
ExternalId = ExternalId,
Role = Role
};
}
}

View file

@ -33,7 +33,7 @@ public class MongoStoragePlugin : IBotSharpPlugin
public bool AttachMenu(List<PluginMenuDef> menu)
{
var section = menu.First(x => x.Label == "Apps");
menu.Add(new PluginMenuDef("MongoDB", icon: "bx bx-data", link: "/page/mongodb", weight: section.Weight + 10));
menu.Add(new PluginMenuDef("MongoDB", icon: "bx bx-data", link: "page/mongodb", weight: section.Weight + 10));
return true;
}
}

View file

@ -7,36 +7,21 @@ public partial class MongoRepository
{
public User? GetUserByEmail(string email)
{
var user = _dc.Users.AsQueryable().FirstOrDefault(x => x.Email == email);
return user != null ? new User
{
Id = user.Id,
UserName = user.UserName,
FirstName = user.FirstName,
LastName = user.LastName,
Email = user.Email,
Password = user.Password,
Salt = user.Salt,
ExternalId = user.ExternalId,
Role = user.Role
} : null;
var user = _dc.Users.AsQueryable().FirstOrDefault(x => x.Email == email.ToLower());
return user != null ? user.ToUser() : null;
}
public User? GetUserById(string id)
{
var user = _dc.Users.AsQueryable().FirstOrDefault(x => x.Id == id || x.ExternalId == id);
return user != null ? new User
{
Id = user.Id,
UserName = user.UserName,
FirstName = user.FirstName,
LastName = user.LastName,
Email = user.Email,
Password = user.Password,
Salt = user.Salt,
ExternalId = user.ExternalId,
Role = user.Role
} : null;
var user = _dc.Users.AsQueryable()
.FirstOrDefault(x => x.Id == id || (x.ExternalId != null && x.ExternalId == id));
return user != null ? user.ToUser() : null;
}
public User? GetUserByUserName(string userName)
{
var user = _dc.Users.AsQueryable().FirstOrDefault(x => x.UserName == userName.ToLower());
return user != null ? user.ToUser() : null;
}
public void CreateUser(User user)
@ -45,13 +30,14 @@ public partial class MongoRepository
var userCollection = new UserDocument
{
Id = Guid.NewGuid().ToString(),
Id = user.Id ?? Guid.NewGuid().ToString(),
UserName = user.UserName,
FirstName = user.FirstName,
LastName = user.LastName,
Salt = user.Salt,
Password = user.Password,
Email = user.Email,
Source = user.Source,
ExternalId = user.ExternalId,
Role = user.Role,
CreatedTime = DateTime.UtcNow,

View file

@ -1,12 +1,10 @@
using BotSharp.Plugin.WebDriver.Services;
using System.Threading;
namespace BotSharp.Plugin.WebDriver.Drivers.PlaywrightDriver;
public partial class PlaywrightWebDriver
{
public async Task ChangeListValue(Agent agent, BrowsingContextIn context, string messageId)
{
await _instance.Page.WaitForLoadStateAsync(LoadState.DOMContentLoaded);
// Retrieve the page raw html and infer the element path
var body = await _instance.Page.QuerySelectorAsync("body");

View file

@ -4,6 +4,7 @@ public partial class PlaywrightWebDriver
{
public async Task ClickElement(Agent agent, BrowsingContextIn context, string messageId)
{
await _instance.Page.WaitForLoadStateAsync(LoadState.DOMContentLoaded);
var driverService = _services.GetRequiredService<WebDriverService>();
// Retrieve the page raw html and infer the element path
@ -37,5 +38,6 @@ public partial class PlaywrightWebDriver
messageId);
ILocator element = Locator(htmlElementContextOut);
await element.ClickAsync();
await _instance.Page.WaitForLoadStateAsync(LoadState.NetworkIdle);
}
}

View file

@ -0,0 +1,12 @@
using Microsoft.Extensions.Logging;
namespace BotSharp.Plugin.WebDriver.Drivers.PlaywrightDriver;
public partial class PlaywrightWebDriver
{
public async Task CloseBrowser(Agent agent, BrowsingContextIn context, string messageId)
{
// await _instance.Browser.CloseAsync();
_logger.LogInformation($"{agent.Name} closed browser with page {_instance.Page.Url}");
}
}

View file

@ -1,11 +1,11 @@
using BotSharp.Plugin.WebDriver.Services;
namespace BotSharp.Plugin.WebDriver.Drivers.PlaywrightDriver;
public partial class PlaywrightWebDriver
{
public async Task<string> ExtractData(Agent agent, BrowsingContextIn context, string messageId)
{
await _instance.Page.WaitForLoadStateAsync(LoadState.DOMContentLoaded);
// Retrieve the page raw html and infer the element path
var body = await _instance.Page.QuerySelectorAsync("body");
var content = await body.InnerTextAsync();

View file

@ -0,0 +1,10 @@
namespace BotSharp.Plugin.WebDriver.Drivers.PlaywrightDriver;
public partial class PlaywrightWebDriver
{
public async Task GoToPage(Agent agent, BrowsingContextIn context, string messageId)
{
await _instance.Page.GotoAsync(context.Url);
await _instance.Page.WaitForLoadStateAsync(LoadState.DOMContentLoaded);
}
}

View file

@ -1,11 +1,11 @@
using Microsoft.Extensions.Configuration;
namespace BotSharp.Plugin.WebDriver.Drivers.PlaywrightDriver;
public partial class PlaywrightWebDriver
{
public async Task InputUserPassword(Agent agent, BrowsingContextIn context, string messageId)
{
await _instance.Page.WaitForLoadStateAsync(LoadState.DOMContentLoaded);
// Retrieve the page raw html and infer the element path
var body = await _instance.Page.QuerySelectorAsync("body");

View file

@ -4,6 +4,8 @@ public partial class PlaywrightWebDriver
{
public async Task InputUserText(Agent agent, BrowsingContextIn context, string messageId)
{
await _instance.Page.WaitForLoadStateAsync(LoadState.DOMContentLoaded);
// await _instance.Page.WaitForLoadStateAsync(LoadState.NetworkIdle);
var driverService = _services.GetRequiredService<WebDriverService>();
// Retrieve the page raw html and infer the element path
@ -17,12 +19,15 @@ public partial class PlaywrightWebDriver
var id = await input.GetAttributeAsync("id");
var name = await input.GetAttributeAsync("name");
var type = await input.GetAttributeAsync("type");
var placeholder = await input.GetAttributeAsync("placeholder");
str.Add(driverService.AssembleMarkup("input", new MarkupProperties
{
Id = id,
Name = name,
Type = type,
Text = text
Text = text,
Placeholder = placeholder
}));
}
@ -33,12 +38,14 @@ public partial class PlaywrightWebDriver
var id = await input.GetAttributeAsync("id");
var name = await input.GetAttributeAsync("name");
var type = await input.GetAttributeAsync("type");
var placeholder = await input.GetAttributeAsync("placeholder");
str.Add(driverService.AssembleMarkup("textarea", new MarkupProperties
{
Id = id,
Name = name,
Type = type,
Text = text
Text = text,
Placeholder = placeholder
}));
}

View file

@ -14,6 +14,7 @@ public partial class PlaywrightWebDriver
});
_instance.SetPage(page);
var response = await page.GotoAsync(url);
await page.WaitForLoadStateAsync(LoadState.DOMContentLoaded);
}
return _instance.Browser;

View file

@ -1,15 +1,19 @@
using Microsoft.Extensions.Logging;
namespace BotSharp.Plugin.WebDriver.Drivers.PlaywrightDriver;
public partial class PlaywrightWebDriver
{
private readonly IServiceProvider _services;
private readonly PlaywrightInstance _instance;
private readonly ILogger _logger;
public PlaywrightInstance Instance => _instance;
public PlaywrightWebDriver(IServiceProvider services, PlaywrightInstance instance)
public PlaywrightWebDriver(IServiceProvider services, PlaywrightInstance instance, ILogger<PlaywrightWebDriver> logger)
{
_services = services;
_instance = instance;
_logger = logger;
}
private ILocator Locator(HtmlElementContextOut context)
@ -20,6 +24,23 @@ public partial class PlaywrightWebDriver
// await _instance.Page.WaitForSelectorAsync($"#{htmlElementContextOut.ElementId}", new PageWaitForSelectorOptions { Timeout = 3 });
element = _instance.Page.Locator($"#{context.ElementId}");
}
else if (!string.IsNullOrEmpty(context.ElementName))
{
var role = context.TagName switch
{
"input" => AriaRole.Textbox,
"textarea" => AriaRole.Textbox,
"button" => AriaRole.Button,
_ => AriaRole.Generic
};
// await _instance.Page.WaitForSelectorAsync($"#{htmlElementContextOut.ElementId}", new PageWaitForSelectorOptions { Timeout = 3 });
element = _instance.Page.Locator($"[name='{context.ElementName}']");
if (element.CountAsync().Result == 0)
{
_logger.LogError($"Can't locate element {role} {context.ElementName}");
}
}
else
{
if (context.Index < 0)

View file

@ -23,10 +23,9 @@ public class ChangeListValueFn : IFunctionCallback
var agentService = _services.GetRequiredService<IAgentService>();
var agent = await agentService.LoadAgent(message.CurrentAgentId);
await _driver.Instance.Page.WaitForLoadStateAsync(LoadState.Load);
await _driver.ChangeListValue(agent, args, message.MessageId);
message.Content = $"Updat the value of \"${args.ElementName}\" to \"{args.UpdateValue}\" successfully.";
message.Content = $"Updat the value of \"{args.ElementName}\" to \"{args.UpdateValue}\" successfully.";
return true;
}
}

View file

@ -23,7 +23,6 @@ public class ClickButtonFn : IFunctionCallback
var agentService = _services.GetRequiredService<IAgentService>();
var agent = await agentService.LoadAgent(message.CurrentAgentId);
await _driver.Instance.Page.WaitForLoadStateAsync(LoadState.Load);
await _driver.ClickElement(agent, args, message.MessageId);
message.Content = $"Click button {args.ElementName} successfully.";

View file

@ -0,0 +1,28 @@
using BotSharp.Plugin.WebDriver.Drivers.PlaywrightDriver;
namespace BotSharp.Plugin.WebDriver.Functions;
public class CloseBrowserFn : IFunctionCallback
{
public string Name => "close_browser";
private readonly IServiceProvider _services;
private readonly PlaywrightWebDriver _driver;
public CloseBrowserFn(IServiceProvider services,
PlaywrightWebDriver driver)
{
_services = services;
_driver = driver;
}
public async Task<bool> Execute(RoleDialogModel message)
{
var args = JsonSerializer.Deserialize<BrowsingContextIn>(message.FunctionArgs);
var agentService = _services.GetRequiredService<IAgentService>();
var agent = await agentService.LoadAgent(message.CurrentAgentId);
await _driver.CloseBrowser(agent, args, message.MessageId);
message.Content = $"Browser is closed";
return true;
}
}

View file

@ -1,5 +1,3 @@
using BotSharp.Abstraction.Agents;
using BotSharp.Plugin.WebDriver.Drivers.PlaywrightDriver;
namespace BotSharp.Plugin.WebDriver.Functions;
@ -23,7 +21,6 @@ public class ExtractDataFn : IFunctionCallback
var args = JsonSerializer.Deserialize<BrowsingContextIn>(message.FunctionArgs);
var agentService = _services.GetRequiredService<IAgentService>();
var agent = await agentService.LoadAgent(message.CurrentAgentId);
await _driver.Instance.Page.WaitForLoadStateAsync(LoadState.Load);
message.Content = await _driver.ExtractData(agent, args, message.MessageId);
return true;
}

View file

@ -0,0 +1,28 @@
using BotSharp.Plugin.WebDriver.Drivers.PlaywrightDriver;
namespace BotSharp.Plugin.WebDriver.Functions;
public class GoToPageFn : IFunctionCallback
{
public string Name => "go_to_page";
private readonly IServiceProvider _services;
private readonly PlaywrightWebDriver _driver;
public GoToPageFn(IServiceProvider services,
PlaywrightWebDriver driver)
{
_services = services;
_driver = driver;
}
public async Task<bool> Execute(RoleDialogModel message)
{
var args = JsonSerializer.Deserialize<BrowsingContextIn>(message.FunctionArgs);
var agentService = _services.GetRequiredService<IAgentService>();
var agent = await agentService.LoadAgent(message.CurrentAgentId);
await _driver.GoToPage(agent, args, message.MessageId);
message.Content = $"Page {args.Url} is open.";
return true;
}
}

View file

@ -23,7 +23,6 @@ public class InputUserPasswordFn : IFunctionCallback
var agentService = _services.GetRequiredService<IAgentService>();
var agent = await agentService.LoadAgent(message.CurrentAgentId);
await _driver.Instance.Page.WaitForLoadStateAsync(LoadState.Load);
await _driver.InputUserPassword(agent, args, message.MessageId);
message.Content = "Input password successfully";

View file

@ -23,7 +23,6 @@ public class InputUserTextFn : IFunctionCallback
var agentService = _services.GetRequiredService<IAgentService>();
var agent = await agentService.LoadAgent(message.CurrentAgentId);
await _driver.Instance.Page.WaitForLoadStateAsync(LoadState.Load);
await _driver.InputUserText(agent, args, message.MessageId);
message.Content = $"Input text \"{args.InputText}\" successfully.";

View file

@ -7,6 +7,9 @@ public class HtmlElementContextOut
[JsonPropertyName("element_id")]
public string ElementId { get; set; }
[JsonPropertyName("element_name")]
public string ElementName { get; set; }
[JsonPropertyName("tag_name")]
public string TagName { get; set; }

View file

@ -6,4 +6,5 @@ internal class MarkupProperties
public string? Name { get; set; }
public string? Type { get; set; }
public string? Text { get; set; }
public string? Placeholder { get; set; }
}

View file

@ -22,6 +22,11 @@ public partial class WebDriverService
html += $" type=\"{properties.Type}\"";
}
if (!string.IsNullOrEmpty(properties.Placeholder))
{
html += $" placeholder=\"{properties.Placeholder}\"";
}
if (!string.IsNullOrEmpty(properties.Text))
{
html += $">{properties.Text}</{tagName}>";

View file

@ -6,6 +6,7 @@ global using BotSharp.Abstraction.Plugins;
global using System.Text.Json;
global using BotSharp.Abstraction.Conversations.Models;
global using Microsoft.Playwright;
global using Microsoft.Extensions.Configuration;
global using BotSharp.Plugin.WebDriver.Drivers;
global using System.Threading.Tasks;
global using BotSharp.Abstraction.Functions;
@ -14,6 +15,7 @@ global using BotSharp.Abstraction.Templating;
global using BotSharp.Plugin.WebDriver.LlmContexts;
global using Microsoft.Extensions.DependencyInjection;
global using System.Linq;
global using BotSharp.Abstraction.Agents;
global using BotSharp.Abstraction.Utilities;
global using BotSharp.Plugin.WebDriver.Models;
global using BotSharp.Plugin.WebDriver.Services;

View file

@ -8,6 +8,6 @@
"isPublic": true,
"profiles": [ "web-driver" ],
"llmConfig": {
"max_recursion_depth": 5
"max_recursion_depth": 10
}
}

View file

@ -1,98 +1,122 @@
[
{
"name": "open_browser",
"description": "open a browser",
"parameters": {
"type": "object",
"properties": {
"url": {
"type": "string",
"description": "website url starts with https://"
}
},
"required": ["url"]
}
},
{
"name": "click_button",
"description": "Click a button in a web page.",
"parameters": {
"type": "object",
"properties": {
"element_name": {
"type": "string",
"description": "the html element name."
}
},
"required": ["element_name"]
}
},
{
"name": "extract_data_from_page",
"description": "Extract data from current web page.",
"parameters": {
"type": "object",
"properties": {
"question": {
"type": "string",
"description": "the information user wants to know"
}
},
"required": ["question"]
}
},
{
"name": "input_user_text",
"description": "Input non-sensitive text in current web page.",
"parameters": {
"type": "object",
"properties": {
"element_name": {
"type": "string",
"description": "the html input box element name."
},
"input_text": {
"type": "string",
"description": "non-sensitive text user provided."
},
"press_enter": {
"type": "boolean",
"description": "whether to press ENTER"
}
},
"required": ["element_name", "input_text"]
}
},
{
"name": "change_list_value",
"description": "Update value from dropdown list or radio button",
"parameters": {
"type": "object",
"properties": {
"element_name": {
"type": "string",
"description": "the html selection element name."
},
"update_value": {
"type": "string",
"description": "the value in the list."
}
},
"required": ["element_name", "update_value"]
}
},
{
"name": "input_user_password",
"description": "Input password in current web page",
"parameters": {
"type": "object",
"properties": {
"password": {
"type": "string",
"description": "user password"
}
},
"required": ["password"]
[
{
"name": "open_browser",
"description": "open a browser",
"parameters": {
"type": "object",
"properties": {
"url": {
"type": "string",
"description": "website url starts with https://"
}
},
"required": [ "url" ]
}
},
{
"name": "close_browser",
"description": "Close browser",
"parameters": {
"type": "object",
"properties": {
},
"required": []
}
},
{
"name": "go_to_page",
"description": "go to another page",
"parameters": {
"type": "object",
"properties": {
"url": {
"type": "string",
"description": "page url start with https://"
}
},
"required": [ "url" ]
}
},
{
"name": "click_button",
"description": "Click a button in a web page.",
"parameters": {
"type": "object",
"properties": {
"element_name": {
"type": "string",
"description": "the html element name."
}
},
"required": [ "element_name" ]
}
},
{
"name": "extract_data_from_page",
"description": "Extract data from current web page.",
"parameters": {
"type": "object",
"properties": {
"question": {
"type": "string",
"description": "the information user wants to know"
}
},
"required": [ "question" ]
}
},
{
"name": "input_user_text",
"description": "Input non-sensitive text in current web page.",
"parameters": {
"type": "object",
"properties": {
"element_name": {
"type": "string",
"description": "the html input box element name."
},
"input_text": {
"type": "string",
"description": "non-sensitive text user provided."
},
"press_enter": {
"type": "boolean",
"description": "whether to press ENTER"
}
},
"required": [ "element_name", "input_text" ]
}
},
{
"name": "change_list_value",
"description": "Update value from dropdown list or radio button",
"parameters": {
"type": "object",
"properties": {
"element_name": {
"type": "string",
"description": "the html selection element name."
},
"update_value": {
"type": "string",
"description": "the value in the list."
}
},
"required": [ "element_name", "update_value" ]
}
},
{
"name": "input_user_password",
"description": "Input password in current web page",
"parameters": {
"type": "object",
"properties": {
"password": {
"type": "string",
"description": "user password"
}
},
"required": [ "password" ]
}
}
]

View file

@ -7,4 +7,5 @@ Follow below steps to response:
Additional response requirements:
* Call function input_user_password if user wants to input password.
* Don't do extra steps if user didn't ask.
* Don't do extra steps if user didn't ask.
* Don't miss any steps.

View file

@ -2,5 +2,5 @@
=== According to above HTML ===
Find the html element in the similar meaning of "{{ element_name }}".
Output in JSON format {"tag_name": "", "element_id": "populated if element has id", "index": -1} with appropriate values.
Output in JSON format {"tag_name": "", "element_id": "populated if element has id", "element_name": "populated if element has name", "index": -1}.
The index is the position of the element which starts with 0.

View file

@ -82,7 +82,7 @@
"EnableLlmCompletionLog": false,
"EnableExecutionLog": true
},
"Stats": {
"Statistics": {
"DataDir": "stats"
},
"LlamaSharp": {

View file

@ -3,6 +3,7 @@
"name": "Pizza Bot",
"description": "AI assistant that can help customer place pizza order.",
"type": "routing",
"inheritAgentId": "01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a",
"createdDateTime": "2023-08-18T10:39:32.2349685Z",
"updatedDateTime": "2023-08-18T14:39:32.2349686Z",
"iconUrl": "https://cdn-icons-png.flaticon.com/512/6978/6978255.png",