From 24319f52138a04b2aa9010de53e8bceb1b5b961d Mon Sep 17 00:00:00 2001 From: Haiping Chen Date: Wed, 10 Jan 2024 08:18:59 -0600 Subject: [PATCH] SPA test --- README.md | 6 +- .../BotSharp.Abstraction.csproj | 2 + .../BotSharp.OpenAPI/BotSharp.OpenAPI.csproj | 1 + .../BotSharpOpenApiExtensions.cs | 61 ++++++++++++++----- .../BotSharp.Plugin.ChatHub/ChatHubPlugin.cs | 2 + .../ChatHubServiceExtensions.cs | 51 ++++++++++++++++ .../Hooks/StreamingLogHook.cs | 49 +++++++++++++++ .../BotSharp.Plugin.ChatHub/SignalRHub.cs | 5 ++ .../Functions/SearchKnowledgesFn.cs | 1 - .../PlaywrightWebDriver.ChangeListValue.cs | 49 +++++++++++++++ .../Functions/ChangeListValueFn.cs | 31 ++++++++++ .../LlmContexts/BrowsingContextIn.cs | 3 + src/WebStarter/Program.cs | 28 ++++----- src/WebStarter/WebStarter.csproj | 7 +-- src/WebStarter/appsettings.json | 3 +- 15 files changed, 258 insertions(+), 41 deletions(-) create mode 100644 src/Plugins/BotSharp.Plugin.ChatHub/ChatHubServiceExtensions.cs create mode 100644 src/Plugins/BotSharp.Plugin.ChatHub/Hooks/StreamingLogHook.cs create mode 100644 src/Plugins/BotSharp.Plugin.WebDriver/Drivers/PlaywrightDriver/PlaywrightWebDriver.ChangeListValue.cs create mode 100644 src/Plugins/BotSharp.Plugin.WebDriver/Functions/ChangeListValueFn.cs diff --git a/README.md b/README.md index 4c1e238f..12541290 100644 --- a/README.md +++ b/README.md @@ -43,11 +43,13 @@ It's written in C# running on .Net Core that is full cross-platform framework, t ```sh PS D:\> git clone https://github.com/SciSharp/BotSharp-UI PS D:\> cd BotSharp-UI -PS D:\> npm install --force +PS D:\> npm install PS D:\> npm run dev ``` -Access http://localhost:5015/ +Access http://localhost:5015/ + +[Online Demo with UI](https://botsharp.azurewebsites.net/) diff --git a/src/Infrastructure/BotSharp.Abstraction/BotSharp.Abstraction.csproj b/src/Infrastructure/BotSharp.Abstraction/BotSharp.Abstraction.csproj index fa407847..37fee33c 100644 --- a/src/Infrastructure/BotSharp.Abstraction/BotSharp.Abstraction.csproj +++ b/src/Infrastructure/BotSharp.Abstraction/BotSharp.Abstraction.csproj @@ -29,6 +29,8 @@ + + diff --git a/src/Infrastructure/BotSharp.OpenAPI/BotSharp.OpenAPI.csproj b/src/Infrastructure/BotSharp.OpenAPI/BotSharp.OpenAPI.csproj index a5166571..36adcf88 100644 --- a/src/Infrastructure/BotSharp.OpenAPI/BotSharp.OpenAPI.csproj +++ b/src/Infrastructure/BotSharp.OpenAPI/BotSharp.OpenAPI.csproj @@ -20,6 +20,7 @@ + diff --git a/src/Infrastructure/BotSharp.OpenAPI/BotSharpOpenApiExtensions.cs b/src/Infrastructure/BotSharp.OpenAPI/BotSharpOpenApiExtensions.cs index 89b9555f..44194444 100644 --- a/src/Infrastructure/BotSharp.OpenAPI/BotSharpOpenApiExtensions.cs +++ b/src/Infrastructure/BotSharp.OpenAPI/BotSharpOpenApiExtensions.cs @@ -1,26 +1,55 @@ -using BotSharp.Abstraction.Messaging.JsonConverters; -using Microsoft.Extensions.Configuration; +using Microsoft.AspNetCore.Builder; namespace BotSharp.OpenAPI; public static class BotSharpOpenApiExtensions { - public static IServiceCollection AddBotSharpOpenAPI(this IServiceCollection services, IConfiguration config) + /// + /// Use Swagger/OpenAPI + /// + /// + /// + /// + public static IApplicationBuilder UseBotSharpOpenAPI(this IApplicationBuilder app, bool isDevelopment = false) { - // Add services to the container. - services.AddControllers() - .AddJsonOptions(options => + if (app == null) + { + throw new ArgumentNullException(nameof(app)); + } + + app.UseSwagger(); + if (isDevelopment) + { + app.UseSwaggerUI(); + } + + return app; + } + + /// + /// Host BotSharp UI built in adapter-static + /// + /// + /// + /// + public static IApplicationBuilder UseBotSharpUI(this IApplicationBuilder app, bool isDevelopment = false) + { + if (app == null) + { + throw new ArgumentNullException(nameof(app)); + } + + // app.UseFileServer(); + app.UseDefaultFiles(); + app.UseStaticFiles(); + app.UseSpa(config => + { + if (isDevelopment) { - options.JsonSerializerOptions.Converters.Add(new RichContentJsonConverter()); - options.JsonSerializerOptions.Converters.Add(new TemplateMessageJsonConverter()); - }); + config.UseProxyToSpaDevelopmentServer("http://localhost:5015"); + } + }); - // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle - services.AddEndpointsApiExplorer(); - services.AddSwaggerGen(); - - services.AddHttpContextAccessor(); - - return services; + return app; } } diff --git a/src/Plugins/BotSharp.Plugin.ChatHub/ChatHubPlugin.cs b/src/Plugins/BotSharp.Plugin.ChatHub/ChatHubPlugin.cs index b085bef7..23e7e6d0 100644 --- a/src/Plugins/BotSharp.Plugin.ChatHub/ChatHubPlugin.cs +++ b/src/Plugins/BotSharp.Plugin.ChatHub/ChatHubPlugin.cs @@ -1,3 +1,4 @@ +using BotSharp.Abstraction.Loggers; using BotSharp.Plugin.ChatHub.Hooks; using Microsoft.Extensions.Configuration; @@ -16,5 +17,6 @@ public class ChatHubPlugin : IBotSharpPlugin { // Register hooks services.AddScoped(); + services.AddScoped(); } } diff --git a/src/Plugins/BotSharp.Plugin.ChatHub/ChatHubServiceExtensions.cs b/src/Plugins/BotSharp.Plugin.ChatHub/ChatHubServiceExtensions.cs new file mode 100644 index 00000000..c67685e5 --- /dev/null +++ b/src/Plugins/BotSharp.Plugin.ChatHub/ChatHubServiceExtensions.cs @@ -0,0 +1,51 @@ +using BotSharp.Abstraction.Messaging.JsonConverters; +using Microsoft.AspNetCore.Builder; +using Microsoft.Extensions.Configuration; + +namespace BotSharp.Plugin.ChatHub; + +public static class ChatHubServiceExtensions +{ + /// + /// Add Swagger/OpenAPI + /// + /// + /// + /// + public static IServiceCollection AddBotSharpOpenAPI(this IServiceCollection services, IConfiguration config) + { + // Add services to the container. + services.AddControllers() + .AddJsonOptions(options => + { + options.JsonSerializerOptions.Converters.Add(new RichContentJsonConverter()); + options.JsonSerializerOptions.Converters.Add(new TemplateMessageJsonConverter()); + }); + + // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle + services.AddEndpointsApiExplorer(); + services.AddSwaggerGen(); + + services.AddHttpContextAccessor(); + + return services; + } + + /// + /// Host BotSharp UI built in adapter-static + /// + /// + /// + /// + public static IApplicationBuilder UseChatHub(this IApplicationBuilder app) + { + if (app == null) + { + throw new ArgumentNullException(nameof(app)); + } + + app.UseMiddleware(); + + return app; + } +} diff --git a/src/Plugins/BotSharp.Plugin.ChatHub/Hooks/StreamingLogHook.cs b/src/Plugins/BotSharp.Plugin.ChatHub/Hooks/StreamingLogHook.cs new file mode 100644 index 00000000..d521badc --- /dev/null +++ b/src/Plugins/BotSharp.Plugin.ChatHub/Hooks/StreamingLogHook.cs @@ -0,0 +1,49 @@ +using BotSharp.Abstraction.Agents.Models; +using BotSharp.Abstraction.Loggers; +using BotSharp.Abstraction.Users.Models; +using Microsoft.AspNetCore.SignalR; + +namespace BotSharp.Plugin.ChatHub.Hooks; + +public class StreamingLogHook : IContentGeneratingHook +{ + private readonly ConversationSetting _convSettings; + private readonly IServiceProvider _services; + private readonly IHubContext _chatHub; + + public StreamingLogHook( + ConversationSetting convSettings, + IServiceProvider serivces, + IHubContext chatHub) + { + _convSettings = convSettings; + _services = serivces; + _chatHub = chatHub; + } + + public async Task BeforeGenerating(Agent agent, List conversations) + { + if (!_convSettings.ShowVerboseLog) return; + + var user = _services.GetRequiredService(); + var dialog = conversations.Last(); + var log = $"{dialog.Role}: {dialog.Content} [msg_id: {dialog.MessageId}] ==>"; + await _chatHub.Clients.User(user.Id).SendAsync("OnContentLogGenerated", log); + } + + public async Task AfterGenerated(RoleDialogModel message, TokenStatsModel tokenStats) + { + if (!_convSettings.ShowVerboseLog) return; + + var agentService = _services.GetRequiredService(); + var agent = await agentService.LoadAgent(message.CurrentAgentId); + + var log = message.Role == AgentRole.Function ? + $"[{agent?.Name}]: {message.FunctionName}({message.FunctionArgs})" : + $"[{agent?.Name}]: {message.Content}" + $" <== [msg_id: {message.MessageId}]"; + + var user = _services.GetRequiredService(); + await _chatHub.Clients.User(user.Id).SendAsync("OnContentLogGenerated", tokenStats.Prompt); + await _chatHub.Clients.User(user.Id).SendAsync("OnContentLogGenerated", log); + } +} diff --git a/src/Plugins/BotSharp.Plugin.ChatHub/SignalRHub.cs b/src/Plugins/BotSharp.Plugin.ChatHub/SignalRHub.cs index 0593b94f..f11bbc82 100644 --- a/src/Plugins/BotSharp.Plugin.ChatHub/SignalRHub.cs +++ b/src/Plugins/BotSharp.Plugin.ChatHub/SignalRHub.cs @@ -49,4 +49,9 @@ public class SignalRHub : Hub await base.OnConnectedAsync(); } + + public Task PushEventLog(string message) + { + return Task.CompletedTask; + } } diff --git a/src/Plugins/BotSharp.Plugin.KnowledgeBase/Functions/SearchKnowledgesFn.cs b/src/Plugins/BotSharp.Plugin.KnowledgeBase/Functions/SearchKnowledgesFn.cs index 5d94f9cb..f2bad9bf 100644 --- a/src/Plugins/BotSharp.Plugin.KnowledgeBase/Functions/SearchKnowledgesFn.cs +++ b/src/Plugins/BotSharp.Plugin.KnowledgeBase/Functions/SearchKnowledgesFn.cs @@ -1,5 +1,4 @@ using BotSharp.Abstraction.Functions; -using BotSharp.Abstraction.Routing.Models; using BotSharp.Plugin.KnowledgeBase.LlmContexts; namespace BotSharp.Plugin.KnowledgeBase.Functions; diff --git a/src/Plugins/BotSharp.Plugin.WebDriver/Drivers/PlaywrightDriver/PlaywrightWebDriver.ChangeListValue.cs b/src/Plugins/BotSharp.Plugin.WebDriver/Drivers/PlaywrightDriver/PlaywrightWebDriver.ChangeListValue.cs new file mode 100644 index 00000000..2c08a0ad --- /dev/null +++ b/src/Plugins/BotSharp.Plugin.WebDriver/Drivers/PlaywrightDriver/PlaywrightWebDriver.ChangeListValue.cs @@ -0,0 +1,49 @@ +using BotSharp.Plugin.WebDriver.Services; + +namespace BotSharp.Plugin.WebDriver.Drivers.PlaywrightDriver; + +public partial class PlaywrightWebDriver +{ + public async Task ChangeListValue(Agent agent, BrowsingContextIn context, string messageId) + { + // Retrieve the page raw html and infer the element path + var body = await _instance.Page.QuerySelectorAsync("body"); + + var str = new List(); + var inputs = await body.QuerySelectorAllAsync("input"); + foreach (var input in inputs) + { + var text = await input.TextContentAsync(); + var name = await input.GetAttributeAsync("name"); + var type = await input.GetAttributeAsync("type"); + str.Add($"{text}"); + } + + inputs = await body.QuerySelectorAllAsync("textarea"); + foreach (var input in inputs) + { + var text = await input.TextContentAsync(); + var name = await input.GetAttributeAsync("name"); + var type = await input.GetAttributeAsync("type"); + str.Add($""); + } + + var driverService = _services.GetRequiredService(); + var htmlElementContextOut = await driverService.LocateElement(agent, string.Join("", str), context.ElementName, messageId); + + if (htmlElementContextOut.Index < 0) + { + throw new Exception($"Can't locate the web element {context.ElementName}."); + } + + var element = _instance.Page.Locator(htmlElementContextOut.TagName).Nth(htmlElementContextOut.Index); + try + { + await element.FillAsync(context.InputText); + } + catch (Exception ex) + { + throw new Exception(ex.Message); + } + } +} diff --git a/src/Plugins/BotSharp.Plugin.WebDriver/Functions/ChangeListValueFn.cs b/src/Plugins/BotSharp.Plugin.WebDriver/Functions/ChangeListValueFn.cs new file mode 100644 index 00000000..4431e1e3 --- /dev/null +++ b/src/Plugins/BotSharp.Plugin.WebDriver/Functions/ChangeListValueFn.cs @@ -0,0 +1,31 @@ +using BotSharp.Abstraction.Agents; +using BotSharp.Plugin.WebDriver.Drivers.PlaywrightDriver; + +namespace BotSharp.Plugin.WebDriver.Functions; + +public class ChangeListValueFn : IFunctionCallback +{ + public string Name => "change_list_value"; + + private readonly IServiceProvider _services; + private readonly PlaywrightWebDriver _driver; + + public ChangeListValueFn(IServiceProvider services, + PlaywrightWebDriver driver) + { + _services = services; + _driver = driver; + } + + public async Task Execute(RoleDialogModel message) + { + var args = JsonSerializer.Deserialize(message.FunctionArgs); + + var agentService = _services.GetRequiredService(); + var agent = await agentService.LoadAgent(message.CurrentAgentId); + await _driver.ChangeListValue(agent, args, message.MessageId); + + message.Content = "Update successfully."; + return true; + } +} diff --git a/src/Plugins/BotSharp.Plugin.WebDriver/LlmContexts/BrowsingContextIn.cs b/src/Plugins/BotSharp.Plugin.WebDriver/LlmContexts/BrowsingContextIn.cs index e914c9db..73c2753b 100644 --- a/src/Plugins/BotSharp.Plugin.WebDriver/LlmContexts/BrowsingContextIn.cs +++ b/src/Plugins/BotSharp.Plugin.WebDriver/LlmContexts/BrowsingContextIn.cs @@ -13,6 +13,9 @@ public class BrowsingContextIn [JsonPropertyName("input_text")] public string? InputText { get; set; } + [JsonPropertyName("update_value")] + public string? UpdateValue { get; set; } + [JsonPropertyName("password")] public string? Password { get; set; } diff --git a/src/WebStarter/Program.cs b/src/WebStarter/Program.cs index 9dd744c7..71850488 100644 --- a/src/WebStarter/Program.cs +++ b/src/WebStarter/Program.cs @@ -11,13 +11,17 @@ using Serilog; var builder = WebApplication.CreateBuilder(args); -Log.Logger = new LoggerConfiguration() +var loggerConfig = new LoggerConfiguration(); +Log.Logger = loggerConfig +#if DEBUG .MinimumLevel.Debug() +#else + .MinimumLevel.Warning() +#endif .WriteTo.Console() .WriteTo.File("logs/log-.txt", rollingInterval: RollingInterval.Day) .CreateLogger(); - -builder.Host.UseSerilog(Log.Logger); +builder.Host.UseSerilog(); builder.Services.AddScoped(); // Add bearer authentication @@ -48,7 +52,8 @@ builder.Services.AddBotSharpLogger(builder.Configuration); builder.Services.AddCors(options => { options.AddPolicy("MyCorsPolicy", - builder => builder.WithOrigins("http://localhost:5015", + builder => builder.WithOrigins("http://localhost:5015", + "http://localhost:5500", "https://botsharp.scisharpstack.org", "https://chat.scisharpstack.org") .AllowAnyMethod() @@ -61,14 +66,8 @@ builder.Services.AddSignalR(); var app = builder.Build(); // Configure the HTTP request pipeline. -app.UseSwagger(); -if (app.Environment.IsDevelopment()) -{ - app.UseSwaggerUI(); -} - app.MapHub("/chatHub"); -app.UseMiddleware(); +app.UseChatHub(); app.UseAuthentication(); app.UseAuthorization(); @@ -77,12 +76,9 @@ app.MapControllers(); // Use BotSharp app.UseBotSharp(); +app.UseBotSharpOpenAPI(); +app.UseBotSharpUI(); app.UseCors("MyCorsPolicy"); -// Host BotSharp UI built in adapter-static -app.UseFileServer(); -app.UseDefaultFiles(); -app.UseStaticFiles(); - app.Run(); diff --git a/src/WebStarter/WebStarter.csproj b/src/WebStarter/WebStarter.csproj index 3eb14e31..ccc84dea 100644 --- a/src/WebStarter/WebStarter.csproj +++ b/src/WebStarter/WebStarter.csproj @@ -29,18 +29,15 @@ - - - - - + + diff --git a/src/WebStarter/appsettings.json b/src/WebStarter/appsettings.json index d15ac9bd..2cb7389a 100644 --- a/src/WebStarter/appsettings.json +++ b/src/WebStarter/appsettings.json @@ -155,7 +155,8 @@ "BotSharp.Plugin.Qdrant", "BotSharp.Plugin.ChatHub", "BotSharp.Plugin.WeChat", - "BotSharp.Plugin.PizzaBot" + "BotSharp.Plugin.PizzaBot", + "BotSharp.Plugin.WebDriver" ] } }