commit
f34249801d
|
|
@ -10,7 +10,7 @@ Building solution using dotnet CLI (preferred).
|
|||
|
||||
### Clone the source code and build
|
||||
```powershell
|
||||
PS D:\> git clone https://github.com/Oceania2018/BotSharp
|
||||
PS D:\> git clone https://github.com/SciSharp/BotSharp
|
||||
PS D:\> cd BotSharp
|
||||
PS D:\> dotnet build
|
||||
```
|
||||
|
|
@ -62,28 +62,28 @@ So far, you have set up the Bot's running and development environment, but you c
|
|||
|
||||
**Ignore below section if you're going to just use REST API to interact with your bot.**
|
||||
|
||||
### Launch a chatbot UI (Optional)
|
||||
You can use a third-party open source UI for debugging and development, or you can directly use the REST API to integrate with your system.
|
||||
If you want to use the [Chatbot UI](https://github.com/mckaywrigley/chatbot-ui) as a front end.
|
||||
### Launch a BotSharp UI (Optional)
|
||||
BotSharp has an official front-end project to be used in conjunction with the backend. The main function of this project is to allow developers to visualize various configurations of the backend.
|
||||
```powershell
|
||||
PS D:\> git clone https://github.com/mckaywrigley/chatbot-ui
|
||||
PS D:\> cd chatbot-ui
|
||||
PS D:\> cd npm i
|
||||
PS D:\> cd npm run dev
|
||||
PS D:\> git clone https://github.com/SciSharp/BotSharp-UI
|
||||
PS D:\> cd BotSharp-UI
|
||||
PS D:\> npm install
|
||||
PS D:\> npm run dev
|
||||
```
|
||||
|
||||
Update API url in `.env.local` to your localhost BotSharp backend service.
|
||||
Update API url in `.env` to your localhost BotSharp backend service.
|
||||
```config
|
||||
OPENAI_API_HOST=http://localhost:5500
|
||||
PUBLIC_SERVICE_URL=http://localhost:5500
|
||||
PUBLIC_LIVECHAT_HOST=http://localhost:5015
|
||||
```
|
||||
|
||||
Point your web browser at http://localhost:3000 and enjoy Chatbot with BotSharp.
|
||||
Point your web browser at http://localhost:5015 and enjoy Chatbot with BotSharp.
|
||||
|
||||

|
||||

|
||||
|
||||
## Install in NuGet
|
||||
If you don't want to use the source code to experience this framework, you can also directly install the [NuGet packages](https://www.nuget.org/packages?q=BotSharp) released by BotSharp, and install different function packages according to the needs of your project. Before installing, please read the documentation carefully to understand the functions that different modules can provide.
|
||||
|
||||
```powershell
|
||||
PS D:\> Install-Package BotSharp.Core
|
||||
```
|
||||
```
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
using BotSharp.Abstraction.Loggers.Models;
|
||||
using BotSharp.Abstraction.Repositories.Filters;
|
||||
|
||||
namespace BotSharp.Abstraction.Conversations;
|
||||
|
|
@ -14,6 +15,8 @@ public interface IConversationService
|
|||
Task<List<Conversation>> GetLastConversations();
|
||||
Task<bool> DeleteConversation(string id);
|
||||
Task<bool> TruncateConversation(string conversationId, string messageId);
|
||||
Task<List<ConversationContentLogModel>> GetConversationContentLogs(string conversationId);
|
||||
Task<List<ConversationStateLogModel>> GetConversationStateLogs(string conversationId);
|
||||
|
||||
/// <summary>
|
||||
/// Send message to LLM
|
||||
|
|
|
|||
|
|
@ -7,6 +7,11 @@ public class Conversation
|
|||
public string Id { get; set; } = string.Empty;
|
||||
public string AgentId { get; set; } = string.Empty;
|
||||
public string UserId { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Agent task id
|
||||
/// </summary>
|
||||
public string? TaskId { get; set; }
|
||||
public string Title { get; set; } = string.Empty;
|
||||
|
||||
[JsonIgnore]
|
||||
|
|
|
|||
|
|
@ -0,0 +1,11 @@
|
|||
using BotSharp.Abstraction.Messaging.Enums;
|
||||
|
||||
namespace BotSharp.Abstraction.Conversations.Models;
|
||||
|
||||
public class ConversationSenderActionModel
|
||||
{
|
||||
[JsonPropertyName("conversation_id")]
|
||||
public string ConversationId { get; set; }
|
||||
[JsonPropertyName("sender_action")]
|
||||
public SenderActionEnum SenderAction { get; set; }
|
||||
}
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
namespace BotSharp.Abstraction.Conversations.Models;
|
||||
|
||||
public class ConversationStateLogModel
|
||||
{
|
||||
[JsonPropertyName("conversation_id")]
|
||||
public string ConvsersationId { get; set; }
|
||||
[JsonPropertyName("states")]
|
||||
public string States { get; set; }
|
||||
[JsonPropertyName("created_at")]
|
||||
public DateTime CreateTime { get; set; }
|
||||
}
|
||||
|
|
@ -9,4 +9,6 @@ public class ConversationSetting
|
|||
public int MaxRecursiveDepth { get; set; } = 3;
|
||||
public bool EnableLlmCompletionLog { get; set; }
|
||||
public bool EnableExecutionLog { get; set; }
|
||||
public bool EnableContentLog { get; set; }
|
||||
public bool EnableStateLog { get; set; }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,15 +1,19 @@
|
|||
namespace BotSharp.Abstraction.Loggers.Models;
|
||||
|
||||
public class StreamingLogModel
|
||||
public class ConversationContentLogModel
|
||||
{
|
||||
[JsonPropertyName("conversation_id")]
|
||||
public string ConversationId { get; set; }
|
||||
[JsonPropertyName("message_id")]
|
||||
public string MessageId { get; set; }
|
||||
[JsonPropertyName("name")]
|
||||
public string? Name { get; set; }
|
||||
[JsonPropertyName("role")]
|
||||
public string Role { get; set; }
|
||||
|
||||
[JsonPropertyName("content")]
|
||||
public string Content { get; set; }
|
||||
|
||||
[JsonPropertyName("created_at")]
|
||||
public DateTime CreateTime { get; set; }
|
||||
public DateTime CreateTime { get; set; } = DateTime.UtcNow;
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
namespace BotSharp.Abstraction.Loggers.Models;
|
||||
|
||||
public class ConversationStateLogModel
|
||||
{
|
||||
[JsonPropertyName("conversation_id")]
|
||||
public string ConversationId { get; set; }
|
||||
[JsonPropertyName("message_id")]
|
||||
public string MessageId { get; set; }
|
||||
[JsonPropertyName("states")]
|
||||
public Dictionary<string, string> States { get; set; }
|
||||
[JsonPropertyName("created_at")]
|
||||
public DateTime CreateTime { get; set; } = DateTime.UtcNow;
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
namespace BotSharp.Abstraction.Conversations.Models;
|
||||
namespace BotSharp.Abstraction.Loggers.Models;
|
||||
|
||||
public class LlmCompletionLog
|
||||
{
|
||||
|
|
@ -1,9 +1,11 @@
|
|||
namespace BotSharp.Plugin.MetaMessenger.MessagingModels;
|
||||
using System.Runtime.Serialization;
|
||||
|
||||
namespace BotSharp.Abstraction.Messaging.Enums;
|
||||
|
||||
public enum SenderActionEnum
|
||||
{
|
||||
[EnumMember(Value = "typing_on")]
|
||||
TypingOn,
|
||||
TypingOn = 1,
|
||||
[EnumMember(Value = "typing_off")]
|
||||
TypingOff,
|
||||
[EnumMember(Value = "mark_seen")]
|
||||
|
|
@ -29,4 +29,9 @@ public class MessageConfig : TruncateMessageRequest
|
|||
/// Conversation states from input
|
||||
/// </summary>
|
||||
public List<string> States { get; set; } = new List<string>();
|
||||
|
||||
/// <summary>
|
||||
/// Agent task id
|
||||
/// </summary>
|
||||
public string? TaskId { get; set; }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,4 +11,9 @@ public class ConversationFilter
|
|||
public string? Status { get; set; }
|
||||
public string? Channel { get; set; }
|
||||
public string? UserId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Agent task id
|
||||
/// </summary>
|
||||
public string? TaskId { get; set; }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
using BotSharp.Abstraction.Loggers.Models;
|
||||
using BotSharp.Abstraction.Plugins.Models;
|
||||
using BotSharp.Abstraction.Repositories.Filters;
|
||||
using BotSharp.Abstraction.Repositories.Models;
|
||||
|
|
@ -70,6 +71,16 @@ public interface IBotSharpRepository
|
|||
void SaveLlmCompletionLog(LlmCompletionLog log);
|
||||
#endregion
|
||||
|
||||
#region Conversation Content Log
|
||||
void SaveConversationContentLog(ConversationContentLogModel log);
|
||||
List<ConversationContentLogModel> GetConversationContentLogs(string conversationId);
|
||||
#endregion
|
||||
|
||||
#region Conversation State Log
|
||||
void SaveConversationStateLog(ConversationStateLogModel log);
|
||||
List<ConversationStateLogModel> GetConversationStateLogs(string conversationId);
|
||||
#endregion
|
||||
|
||||
#region Statistics
|
||||
void IncrementConversationCount();
|
||||
#endregion
|
||||
|
|
|
|||
|
|
@ -0,0 +1,22 @@
|
|||
using BotSharp.Abstraction.Loggers.Models;
|
||||
using BotSharp.Abstraction.Repositories;
|
||||
|
||||
namespace BotSharp.Core.Conversations.Services;
|
||||
|
||||
public partial class ConversationService
|
||||
{
|
||||
public async Task<List<ConversationContentLogModel>> GetConversationContentLogs(string conversationId)
|
||||
{
|
||||
var db = _services.GetRequiredService<IBotSharpRepository>();
|
||||
var logs = db.GetConversationContentLogs(conversationId);
|
||||
return await Task.FromResult(logs);
|
||||
}
|
||||
|
||||
|
||||
public async Task<List<ConversationStateLogModel>> GetConversationStateLogs(string conversationId)
|
||||
{
|
||||
var db = _services.GetRequiredService<IBotSharpRepository>();
|
||||
var logs = db.GetConversationStateLogs(conversationId);
|
||||
return await Task.FromResult(logs);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
using BotSharp.Abstraction.Agents.Models;
|
||||
using BotSharp.Abstraction.Loggers.Models;
|
||||
using BotSharp.Abstraction.Plugins.Models;
|
||||
using BotSharp.Abstraction.Repositories;
|
||||
using BotSharp.Abstraction.Repositories.Filters;
|
||||
|
|
@ -255,6 +256,30 @@ public class BotSharpDbContext : Database, IBotSharpRepository
|
|||
}
|
||||
#endregion
|
||||
|
||||
#region Conversation Content Log
|
||||
public void SaveConversationContentLog(ConversationContentLogModel log)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public List<ConversationContentLogModel> GetConversationContentLogs(string conversationId)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Conversation State Log
|
||||
public void SaveConversationStateLog(ConversationStateLogModel log)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public List<ConversationStateLogModel> GetConversationStateLogs(string conversationId)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Stats
|
||||
public void IncrementConversationCount()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -206,8 +206,13 @@ namespace BotSharp.Core.Repository
|
|||
var records = new List<Conversation>();
|
||||
var dir = Path.Combine(_dbSettings.FileRepository, _conversationSettings.DataDir);
|
||||
var pager = filter?.Pager ?? new Pagination();
|
||||
var totalDirs = Directory.GetDirectories(dir);
|
||||
|
||||
if (!Directory.Exists(dir))
|
||||
{
|
||||
Directory.CreateDirectory(dir);
|
||||
}
|
||||
|
||||
var totalDirs = Directory.GetDirectories(dir);
|
||||
foreach (var d in totalDirs)
|
||||
{
|
||||
var path = Path.Combine(d, CONVERSATION_FILE);
|
||||
|
|
@ -223,6 +228,7 @@ namespace BotSharp.Core.Repository
|
|||
if (filter?.Status != null) matched = matched && record.Status == filter.Status;
|
||||
if (filter?.Channel != null) matched = matched && record.Channel == filter.Channel;
|
||||
if (filter?.UserId != null) matched = matched && record.UserId == filter.UserId;
|
||||
if (filter?.TaskId != null) matched = matched && record.TaskId == filter.TaskId;
|
||||
|
||||
if (!matched) continue;
|
||||
records.Add(record);
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
using BotSharp.Abstraction.Loggers.Models;
|
||||
using Serilog;
|
||||
using System.IO;
|
||||
|
||||
namespace BotSharp.Core.Repository
|
||||
|
|
@ -54,14 +56,112 @@ namespace BotSharp.Core.Repository
|
|||
Directory.CreateDirectory(logDir);
|
||||
}
|
||||
|
||||
var index = GetNextLlmCompletionLogIndex(logDir, log.MessageId);
|
||||
var index = GetNextLogIndex(logDir, log.MessageId);
|
||||
var file = Path.Combine(logDir, $"{log.MessageId}.{index}.log");
|
||||
File.WriteAllText(file, JsonSerializer.Serialize(log, _options));
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Conversation Content Log
|
||||
public void SaveConversationContentLog(ConversationContentLogModel log)
|
||||
{
|
||||
if (log == null) return;
|
||||
|
||||
log.ConversationId = log.ConversationId.IfNullOrEmptyAs(Guid.NewGuid().ToString());
|
||||
log.MessageId = log.MessageId.IfNullOrEmptyAs(Guid.NewGuid().ToString());
|
||||
|
||||
var convDir = FindConversationDirectory(log.ConversationId);
|
||||
if (string.IsNullOrEmpty(convDir))
|
||||
{
|
||||
convDir = Path.Combine(_dbSettings.FileRepository, _conversationSettings.DataDir, log.ConversationId);
|
||||
Directory.CreateDirectory(convDir);
|
||||
}
|
||||
|
||||
var logDir = Path.Combine(convDir, "content_log");
|
||||
if (!Directory.Exists(logDir))
|
||||
{
|
||||
Directory.CreateDirectory(logDir);
|
||||
}
|
||||
|
||||
var index = GetNextLogIndex(logDir, log.MessageId);
|
||||
var file = Path.Combine(logDir, $"{log.MessageId}.{index}.log");
|
||||
File.WriteAllText(file, JsonSerializer.Serialize(log, _options));
|
||||
}
|
||||
|
||||
public List<ConversationContentLogModel> GetConversationContentLogs(string conversationId)
|
||||
{
|
||||
var logs = new List<ConversationContentLogModel>();
|
||||
if (string.IsNullOrEmpty(conversationId)) return logs;
|
||||
|
||||
var convDir = FindConversationDirectory(conversationId);
|
||||
if (string.IsNullOrEmpty(convDir)) return logs;
|
||||
|
||||
var logDir = Path.Combine(convDir, "content_log");
|
||||
if (!Directory.Exists(logDir)) return logs;
|
||||
|
||||
foreach (var file in Directory.GetFiles(logDir))
|
||||
{
|
||||
var text = File.ReadAllText(file);
|
||||
var log = JsonSerializer.Deserialize<ConversationContentLogModel>(text);
|
||||
if (log == null) continue;
|
||||
|
||||
logs.Add(log);
|
||||
}
|
||||
return logs.OrderBy(x => x.CreateTime).ToList();
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Conversation State Log
|
||||
public void SaveConversationStateLog(ConversationStateLogModel log)
|
||||
{
|
||||
if (log == null) return;
|
||||
|
||||
log.ConversationId = log.ConversationId.IfNullOrEmptyAs(Guid.NewGuid().ToString());
|
||||
log.MessageId = log.MessageId.IfNullOrEmptyAs(Guid.NewGuid().ToString());
|
||||
|
||||
var convDir = FindConversationDirectory(log.ConversationId);
|
||||
if (string.IsNullOrEmpty(convDir))
|
||||
{
|
||||
convDir = Path.Combine(_dbSettings.FileRepository, _conversationSettings.DataDir, log.ConversationId);
|
||||
Directory.CreateDirectory(convDir);
|
||||
}
|
||||
|
||||
var logDir = Path.Combine(convDir, "state_log");
|
||||
if (!Directory.Exists(logDir))
|
||||
{
|
||||
Directory.CreateDirectory(logDir);
|
||||
}
|
||||
|
||||
var index = GetNextLogIndex(logDir, log.MessageId);
|
||||
var file = Path.Combine(logDir, $"{log.MessageId}.{index}.log");
|
||||
File.WriteAllText(file, JsonSerializer.Serialize(log, _options));
|
||||
}
|
||||
|
||||
public List<ConversationStateLogModel> GetConversationStateLogs(string conversationId)
|
||||
{
|
||||
var logs = new List<ConversationStateLogModel>();
|
||||
if (string.IsNullOrEmpty(conversationId)) return logs;
|
||||
|
||||
var convDir = FindConversationDirectory(conversationId);
|
||||
if (string.IsNullOrEmpty(convDir)) return logs;
|
||||
|
||||
var logDir = Path.Combine(convDir, "state_log");
|
||||
if (!Directory.Exists(logDir)) return logs;
|
||||
|
||||
foreach (var file in Directory.GetFiles(logDir))
|
||||
{
|
||||
var text = File.ReadAllText(file);
|
||||
var log = JsonSerializer.Deserialize<ConversationStateLogModel>(text);
|
||||
if (log == null) continue;
|
||||
|
||||
logs.Add(log);
|
||||
}
|
||||
return logs.OrderBy(x => x.CreateTime).ToList();
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Private methods
|
||||
private int GetNextLlmCompletionLogIndex(string logDir, string id)
|
||||
private int GetNextLogIndex(string logDir, string id)
|
||||
{
|
||||
var files = Directory.GetFiles(logDir);
|
||||
if (files.IsNullOrEmpty())
|
||||
|
|
|
|||
|
|
@ -14,17 +14,27 @@ public class UserIdentity : IUserIdentity
|
|||
}
|
||||
|
||||
|
||||
public string Id
|
||||
public string Id
|
||||
=> _claims?.FirstOrDefault(x => x.Type == ClaimTypes.NameIdentifier)?.Value!;
|
||||
|
||||
public string UserName
|
||||
=> _claims?.FirstOrDefault(x => x.Type == ClaimTypes.Name)?.Value!;
|
||||
|
||||
public string Email
|
||||
public string Email
|
||||
=> _claims?.FirstOrDefault(x => x.Type == ClaimTypes.Email)?.Value!;
|
||||
|
||||
public string FirstName
|
||||
=> _claims?.FirstOrDefault(x => x.Type == ClaimTypes.GivenName)?.Value!;
|
||||
public string FirstName
|
||||
{
|
||||
get
|
||||
{
|
||||
var givenName = _claims?.FirstOrDefault(x => x.Type == ClaimTypes.GivenName);
|
||||
if (givenName == null)
|
||||
{
|
||||
return UserName;
|
||||
}
|
||||
return givenName.Value;
|
||||
}
|
||||
}
|
||||
|
||||
public string LastName
|
||||
=> _claims?.FirstOrDefault(x => x.Type == ClaimTypes.Surname)?.Value!;
|
||||
|
|
|
|||
|
|
@ -48,7 +48,7 @@ public class UserService : IUserService
|
|||
}
|
||||
|
||||
record = user;
|
||||
record.Email = user.Email.ToLower();
|
||||
record.Email = user.Email?.ToLower();
|
||||
record.Salt = Guid.NewGuid().ToString("N");
|
||||
record.Password = Utilities.HashText(user.Password, record.Salt);
|
||||
|
||||
|
|
@ -167,7 +167,7 @@ public class UserService : IUserService
|
|||
Issuer = issuer,
|
||||
Audience = audience,
|
||||
SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key),
|
||||
SecurityAlgorithms.HmacSha512Signature)
|
||||
SecurityAlgorithms.HmacSha256Signature)
|
||||
};
|
||||
var tokenHandler = new JwtSecurityTokenHandler();
|
||||
var token = tokenHandler.CreateToken(tokenDescriptor);
|
||||
|
|
@ -178,7 +178,15 @@ public class UserService : IUserService
|
|||
public async Task<User> GetMyProfile()
|
||||
{
|
||||
var db = _services.GetRequiredService<IBotSharpRepository>();
|
||||
var user = db.GetUserById(_user.Id);
|
||||
User user = default;
|
||||
if (_user.UserName != null)
|
||||
{
|
||||
user = db.GetUserByUserName(_user.UserName);
|
||||
}
|
||||
else if (_user.Email != null)
|
||||
{
|
||||
user = db.GetUserByEmail(_user.Email);
|
||||
}
|
||||
return user;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@
|
|||
"iconUrl": "https://cdn.iconscout.com/icon/premium/png-256-thumb/route-1613278-1368497.png",
|
||||
"disabled": false,
|
||||
"isPublic": true,
|
||||
"profiles": [ "default" ],
|
||||
"profiles": [ "tool" ],
|
||||
"routingRules": [
|
||||
{
|
||||
"type": "planner",
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
using BotSharp.Abstraction.Loggers.Models;
|
||||
|
||||
public class CommonContentGeneratingHook : IContentGeneratingHook
|
||||
{
|
||||
private readonly IServiceProvider _services;
|
||||
|
|
|
|||
|
|
@ -16,12 +16,25 @@
|
|||
<PackagePath>\</PackagePath>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<FrameworkReference Include="Microsoft.AspNetCore.App" />
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.5.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.SpaServices.Extensions" Version="8.0.1" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="8.0.1" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition="'$(TargetFramework)' == 'net8.0'">
|
||||
<PackageReference Include="Microsoft.AspNetCore.Authentication.Google" Version="8.0.2" />
|
||||
<PackageReference Include="AspNet.Security.OAuth.GitHub" Version="8.0.0" />
|
||||
<PackageReference Include="AspNet.Security.OAuth.Keycloak" Version="8.0.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="8.0.2" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.SpaServices.Extensions" Version="8.0.2" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition="'$(TargetFramework)' == 'net6.0'">
|
||||
<PackageReference Include="Microsoft.AspNetCore.Authentication.Google" Version="6.0.27" />
|
||||
<PackageReference Include="AspNet.Security.OAuth.GitHub" Version="6.0.15" />
|
||||
<PackageReference Include="AspNet.Security.OAuth.Keycloak" Version="6.0.15" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="6.0.25" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.SpaServices.Extensions" Version="6.0.26" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
|
|||
|
|
@ -1,13 +1,15 @@
|
|||
using BotSharp.Abstraction.Messaging.JsonConverters;
|
||||
using BotSharp.Core.Users.Services;
|
||||
using Microsoft.AspNetCore.Authentication.Cookies;
|
||||
using Microsoft.AspNetCore.Authentication.JwtBearer;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.IdentityModel.Logging;
|
||||
using Microsoft.IdentityModel.Tokens;
|
||||
using Microsoft.Net.Http.Headers;
|
||||
using Microsoft.OpenApi.Models;
|
||||
using Swashbuckle.AspNetCore.SwaggerGen;
|
||||
using System.IdentityModel.Tokens.Jwt;
|
||||
using Microsoft.IdentityModel.JsonWebTokens;
|
||||
|
||||
namespace BotSharp.OpenAPI;
|
||||
|
||||
|
|
@ -29,11 +31,14 @@ public static class BotSharpOpenApiExtensions
|
|||
services.AddScoped<IUserIdentity, UserIdentity>();
|
||||
|
||||
// Add bearer authentication
|
||||
services.AddAuthentication(options =>
|
||||
var schema = "MIXED_SCHEME";
|
||||
var builder = services.AddAuthentication(options =>
|
||||
{
|
||||
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
|
||||
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
|
||||
options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
|
||||
// custom scheme defined in .AddPolicyScheme() below
|
||||
// inspired from https://weblog.west-wind.com/posts/2022/Mar/29/Combining-Bearer-Token-and-Cookie-Auth-in-ASPNET
|
||||
options.DefaultScheme = schema;
|
||||
options.DefaultChallengeScheme = schema;
|
||||
options.DefaultAuthenticateScheme = schema;
|
||||
}).AddJwtBearer(o =>
|
||||
{
|
||||
o.TokenValidationParameters = new TokenValidationParameters
|
||||
|
|
@ -50,10 +55,67 @@ public static class BotSharpOpenApiExtensions
|
|||
if (!enableValidation)
|
||||
{
|
||||
o.TokenValidationParameters.SignatureValidator = (string token, TokenValidationParameters parameters) =>
|
||||
new JwtSecurityToken(token);
|
||||
new JsonWebToken(token);
|
||||
}
|
||||
}).AddCookie(options =>
|
||||
{
|
||||
}).AddPolicyScheme(schema, "Mixed authentication", options =>
|
||||
{
|
||||
// runs on each request
|
||||
options.ForwardDefaultSelector = context =>
|
||||
{
|
||||
// filter by auth type
|
||||
string authorization = context.Request.Headers[HeaderNames.Authorization];
|
||||
if (!string.IsNullOrEmpty(authorization) && authorization.StartsWith("Bearer "))
|
||||
return JwtBearerDefaults.AuthenticationScheme;
|
||||
else if (context.Request.Cookies.ContainsKey(".AspNetCore.Cookies"))
|
||||
return CookieAuthenticationDefaults.AuthenticationScheme;
|
||||
else if (context.Request.Path.StartsWithSegments("/sso") && context.Request.Method == "GET")
|
||||
return CookieAuthenticationDefaults.AuthenticationScheme;
|
||||
else if (context.Request.Path.ToString().StartsWith("/signin-") && context.Request.Method == "GET")
|
||||
return CookieAuthenticationDefaults.AuthenticationScheme;
|
||||
|
||||
// otherwise always check for cookie auth
|
||||
return JwtBearerDefaults.AuthenticationScheme;
|
||||
};
|
||||
});
|
||||
|
||||
// GitHub OAuth
|
||||
if (!string.IsNullOrWhiteSpace(config["OAuth:GitHub:ClientId"]) && !string.IsNullOrWhiteSpace(config["OAuth:GitHub:ClientSecret"]))
|
||||
{
|
||||
builder = builder.AddGitHub(options =>
|
||||
{
|
||||
options.ClientId = config["OAuth:GitHub:ClientId"];
|
||||
options.ClientSecret = config["OAuth:GitHub:ClientSecret"];
|
||||
options.Scope.Add("user:email");
|
||||
});
|
||||
}
|
||||
|
||||
// Google Identiy OAuth
|
||||
if (!string.IsNullOrWhiteSpace(config["OAuth:Google:ClientId"]) && !string.IsNullOrWhiteSpace(config["OAuth:Google:ClientSecret"]))
|
||||
{
|
||||
builder = builder.AddGoogle(options =>
|
||||
{
|
||||
options.ClientId = config["OAuth:Google:ClientId"];
|
||||
options.ClientSecret = config["OAuth:Google:ClientSecret"];
|
||||
});
|
||||
}
|
||||
|
||||
// Keycloak Identiy OAuth
|
||||
if (!string.IsNullOrWhiteSpace(config["OAuth:Keycloak:ClientId"]) && !string.IsNullOrWhiteSpace(config["OAuth:Keycloak:ClientSecret"]))
|
||||
{
|
||||
builder = builder.AddKeycloak(options =>
|
||||
{
|
||||
options.BaseAddress = new Uri(config["OAuth:Keycloak:BaseAddress"]);
|
||||
options.Realm = config["OAuth:Keycloak:Realm"];
|
||||
options.ClientId = config["OAuth:Keycloak:ClientId"];
|
||||
options.ClientSecret = config["OAuth:Keycloak:ClientSecret"];
|
||||
options.AccessType = AspNet.Security.OAuth.Keycloak.KeycloakAuthenticationAccessType.Confidential;
|
||||
int version = Convert.ToInt32(config["OAuth:Keycloak:Version"]??"22") ;
|
||||
options.Version = new Version(version,0);
|
||||
});
|
||||
}
|
||||
|
||||
// Add services to the container.
|
||||
services.AddControllers()
|
||||
.AddJsonOptions(options =>
|
||||
|
|
@ -75,18 +137,18 @@ public static class BotSharpOpenApiExtensions
|
|||
Type = SecuritySchemeType.ApiKey
|
||||
});
|
||||
c.AddSecurityRequirement(new OpenApiSecurityRequirement {
|
||||
{
|
||||
new OpenApiSecurityScheme
|
||||
{
|
||||
Reference = new OpenApiReference
|
||||
{
|
||||
Type = ReferenceType.SecurityScheme,
|
||||
Id = "Bearer"
|
||||
new OpenApiSecurityScheme
|
||||
{
|
||||
Reference = new OpenApiReference
|
||||
{
|
||||
Type = ReferenceType.SecurityScheme,
|
||||
Id = "Bearer"
|
||||
}
|
||||
},
|
||||
Array.Empty<string>()
|
||||
}
|
||||
},
|
||||
Array.Empty<string>()
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
|
|
@ -123,6 +185,7 @@ public static class BotSharpOpenApiExtensions
|
|||
|
||||
if (env.IsDevelopment())
|
||||
{
|
||||
IdentityModelEventSource.ShowPII = true;
|
||||
app.UseSwaggerUI();
|
||||
app.UseDeveloperExceptionPage();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,7 +24,8 @@ public class ConversationController : ControllerBase
|
|||
{
|
||||
AgentId = agentId,
|
||||
Channel = ConversationChannel.OpenAPI,
|
||||
UserId = _user.Id
|
||||
UserId = _user.Id,
|
||||
TaskId = config.TaskId
|
||||
};
|
||||
conv = await service.NewConversation(conv);
|
||||
service.SetConversationId(conv.Id, config.States);
|
||||
|
|
@ -50,7 +51,7 @@ public class ConversationController : ControllerBase
|
|||
item.User = UserViewModel.FromUser(user);
|
||||
|
||||
var agent = await agentService.GetAgent(item.AgentId);
|
||||
item.AgentName = agent.Name;
|
||||
item.AgentName = agent?.Name;
|
||||
}
|
||||
|
||||
return new PagedItems<ConversationViewModel>
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
using BotSharp.Abstraction.Loggers.Models;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using SharpCompress.Compressors.Xz;
|
||||
using System;
|
||||
|
|
@ -34,4 +35,18 @@ public class LoggerController : ControllerBase
|
|||
return NotFound();
|
||||
}
|
||||
}
|
||||
|
||||
[HttpGet("/logger/conversation/{conversationId}/content-log")]
|
||||
public async Task<List<ConversationContentLogModel>> GetConversationContentLogs([FromRoute] string conversationId)
|
||||
{
|
||||
var conversationService = _services.GetRequiredService<IConversationService>();
|
||||
return await conversationService.GetConversationContentLogs(conversationId);
|
||||
}
|
||||
|
||||
[HttpGet("/logger/conversation/{conversationId}/state-log")]
|
||||
public async Task<List<ConversationStateLogModel>> GetConversationStateLogs([FromRoute] string conversationId)
|
||||
{
|
||||
var conversationService = _services.GetRequiredService<IConversationService>();
|
||||
return await conversationService.GetConversationStateLogs(conversationId);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
using Microsoft.AspNetCore.Authentication;
|
||||
using Microsoft.AspNetCore.Authentication.Cookies;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace BotSharp.OpenAPI.Controllers;
|
||||
|
|
@ -6,9 +8,11 @@ namespace BotSharp.OpenAPI.Controllers;
|
|||
[ApiController]
|
||||
public class UserController : ControllerBase
|
||||
{
|
||||
private readonly IServiceProvider _services;
|
||||
private readonly IUserService _userService;
|
||||
public UserController(IUserService userService)
|
||||
public UserController(IUserService userService, IServiceProvider services)
|
||||
{
|
||||
_services = services;
|
||||
_userService = userService;
|
||||
}
|
||||
|
||||
|
|
@ -30,6 +34,25 @@ public class UserController : ControllerBase
|
|||
return Ok(token);
|
||||
}
|
||||
|
||||
[AllowAnonymous]
|
||||
[HttpGet("/sso/{provider}")]
|
||||
public async Task<IActionResult> Authorize([FromRoute] string provider,string redirectUrl)
|
||||
{
|
||||
return Challenge(new AuthenticationProperties { RedirectUri = redirectUrl }, provider);
|
||||
}
|
||||
|
||||
[AllowAnonymous]
|
||||
[HttpGet("/signout")]
|
||||
[HttpPost("/signout")]
|
||||
public IActionResult SignOutCurrentUser()
|
||||
{
|
||||
// Instruct the cookies middleware to delete the local cookie created
|
||||
// when the user agent is redirected from the external identity provider
|
||||
// after a successful authentication flow (e.g Google or Facebook).
|
||||
return SignOut(new AuthenticationProperties { RedirectUri = "/" },
|
||||
CookieAuthenticationDefaults.AuthenticationScheme);
|
||||
}
|
||||
|
||||
[AllowAnonymous]
|
||||
[HttpPost("/user")]
|
||||
public async Task<UserViewModel> CreateUser(UserCreationModel user)
|
||||
|
|
@ -38,10 +61,25 @@ public class UserController : ControllerBase
|
|||
return UserViewModel.FromUser(createdUser);
|
||||
}
|
||||
|
||||
[HttpGet("/user/my")]
|
||||
[HttpGet("/user/me")]
|
||||
public async Task<UserViewModel> GetMyUserProfile()
|
||||
{
|
||||
var user = await _userService.GetMyProfile();
|
||||
if (user == null)
|
||||
{
|
||||
var identiy = _services.GetRequiredService<IUserIdentity>();
|
||||
var accessor = _services.GetRequiredService<IHttpContextAccessor>();
|
||||
var claims = accessor.HttpContext.User.Claims;
|
||||
user = await _userService.CreateUser(new User
|
||||
{
|
||||
Email = identiy.Email,
|
||||
UserName = identiy.UserName,
|
||||
FirstName = identiy.FirstName,
|
||||
LastName = identiy.LastName,
|
||||
Source = claims.First().Issuer,
|
||||
ExternalId = identiy.Id,
|
||||
});
|
||||
}
|
||||
return UserViewModel.FromUser(user);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,6 +19,13 @@ public class ConversationViewModel
|
|||
public string Event { get; set; }
|
||||
|
||||
public string Channel { get; set; } = ConversationChannel.OpenAPI;
|
||||
|
||||
/// <summary>
|
||||
/// Agent task id
|
||||
/// </summary>
|
||||
[JsonPropertyName("task_id")]
|
||||
public string? TaskId { get; set; }
|
||||
|
||||
public string Status { get; set; }
|
||||
public Dictionary<string, string> States { get; set; }
|
||||
|
||||
|
|
@ -40,6 +47,7 @@ public class ConversationViewModel
|
|||
Title = sess.Title,
|
||||
Channel = sess.Channel,
|
||||
Status = sess.Status,
|
||||
TaskId = sess.TaskId,
|
||||
CreatedTime = sess.CreatedTime,
|
||||
UpdatedTime = sess.UpdatedTime
|
||||
};
|
||||
|
|
|
|||
|
|
@ -15,7 +15,8 @@ public class UserViewModel
|
|||
public string Email { get; set; }
|
||||
public string Role { get; set; } = UserRole.Client;
|
||||
[JsonPropertyName("full_name")]
|
||||
public string FullName => $"{FirstName} {LastName}";
|
||||
public string FullName => $"{FirstName} {LastName}".Trim();
|
||||
public string Source { get; set; }
|
||||
[JsonPropertyName("external_id")]
|
||||
public string? ExternalId { get; set; }
|
||||
[JsonPropertyName("create_date")]
|
||||
|
|
@ -43,6 +44,7 @@ public class UserViewModel
|
|||
LastName = user.LastName,
|
||||
Email = user.Email,
|
||||
Role = user.Role,
|
||||
Source = user.Source,
|
||||
ExternalId = user.ExternalId,
|
||||
CreateDate = user.CreatedTime,
|
||||
UpdateDate = user.UpdatedTime
|
||||
|
|
|
|||
|
|
@ -1,6 +1,9 @@
|
|||
using BotSharp.Abstraction.Loggers.Models;
|
||||
using BotSharp.Abstraction.Messaging;
|
||||
using BotSharp.Abstraction.Messaging.Enums;
|
||||
using BotSharp.Abstraction.Messaging.JsonConverters;
|
||||
using BotSharp.Abstraction.Messaging.Models.RichContent;
|
||||
using BotSharp.Abstraction.Repositories;
|
||||
using Microsoft.AspNetCore.SignalR;
|
||||
|
||||
namespace BotSharp.Plugin.ChatHub.Hooks;
|
||||
|
|
@ -94,20 +97,26 @@ public class ChatHubConversationHook : ConversationHookBase
|
|||
Sender = UserViewModel.FromUser(sender)
|
||||
});
|
||||
|
||||
// Send typing-on to client
|
||||
await _chatHub.Clients.User(_user.Id).SendAsync("OnSenderActionGenerated", new ConversationSenderActionModel
|
||||
{
|
||||
ConversationId = conv.ConversationId,
|
||||
SenderAction = SenderActionEnum.TypingOn
|
||||
});
|
||||
|
||||
await base.OnMessageReceived(message);
|
||||
}
|
||||
|
||||
public override async Task OnResponseGenerated(RoleDialogModel message)
|
||||
{
|
||||
var conv = _services.GetRequiredService<IConversationService>();
|
||||
var state = _services.GetRequiredService<IConversationStateService>();
|
||||
|
||||
var json = JsonSerializer.Serialize(new ChatResponseModel()
|
||||
{
|
||||
ConversationId = conv.ConversationId,
|
||||
MessageId = message.MessageId,
|
||||
Text = message.Content,
|
||||
RichContent = message.RichContent,
|
||||
Data = message.Data,
|
||||
Sender = new UserViewModel()
|
||||
{
|
||||
FirstName = "AI",
|
||||
|
|
@ -115,21 +124,15 @@ public class ChatHubConversationHook : ConversationHookBase
|
|||
Role = AgentRole.Assistant
|
||||
}
|
||||
}, _serializerOptions);
|
||||
|
||||
// Send typing-off to client
|
||||
await _chatHub.Clients.User(_user.Id).SendAsync("OnSenderActionGenerated", new ConversationSenderActionModel
|
||||
{
|
||||
ConversationId = conv.ConversationId,
|
||||
SenderAction = SenderActionEnum.TypingOff
|
||||
});
|
||||
await _chatHub.Clients.User(_user.Id).SendAsync("OnMessageReceivedFromAssistant", json);
|
||||
await _chatHub.Clients.User(_user.Id).SendAsync("OnConversateStatesGenerated", BuildConversationStates(conv.ConversationId, state.GetStates()));
|
||||
|
||||
await base.OnResponseGenerated(message);
|
||||
}
|
||||
|
||||
private string BuildConversationStates(string conversationId, Dictionary<string, string> states)
|
||||
{
|
||||
var model = new ConversationStateLogModel
|
||||
{
|
||||
ConvsersationId = conversationId,
|
||||
States = JsonSerializer.Serialize(states, _serializerOptions),
|
||||
CreateTime = DateTime.UtcNow
|
||||
};
|
||||
|
||||
return JsonSerializer.Serialize(model, _serializerOptions);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
using BotSharp.Abstraction.Agents.Models;
|
||||
using BotSharp.Abstraction.Loggers;
|
||||
using BotSharp.Abstraction.Loggers.Models;
|
||||
using BotSharp.Abstraction.Repositories;
|
||||
using Microsoft.AspNetCore.SignalR;
|
||||
|
||||
namespace BotSharp.Plugin.ChatHub.Hooks;
|
||||
|
|
@ -37,7 +38,7 @@ public class StreamingLogHook : ConversationHookBase, IContentGeneratingHook
|
|||
{
|
||||
var conversationId = _state.GetConversationId();
|
||||
var log = $"MessageId: {message.MessageId} ==>\r\n{message.Role}: {message.Content}";
|
||||
await _chatHub.Clients.User(_user.Id).SendAsync("OnContentLogGenerated", BuildLog(conversationId, _user.UserName, log));
|
||||
await _chatHub.Clients.User(_user.Id).SendAsync("OnConversationContentLogGenerated", BuildContentLog(conversationId, _user.UserName, log, message));
|
||||
}
|
||||
|
||||
public async Task BeforeGenerating(Agent agent, List<RoleDialogModel> conversations)
|
||||
|
|
@ -64,24 +65,62 @@ public class StreamingLogHook : ConversationHookBase, IContentGeneratingHook
|
|||
var conversationId = _state.GetConversationId();
|
||||
var agent = await agentService.LoadAgent(message.CurrentAgentId);
|
||||
|
||||
await _chatHub.Clients.User(_user.Id).SendAsync("OnContentLogGenerated", BuildLog(conversationId, agent?.Name, tokenStats.Prompt));
|
||||
await _chatHub.Clients.User(_user.Id).SendAsync("OnConversationContentLogGenerated", BuildContentLog(conversationId, agent?.Name, tokenStats.Prompt, message));
|
||||
|
||||
var log = message.Role == AgentRole.Function ?
|
||||
$"[{agent?.Name}]: {message.FunctionName}({message.FunctionArgs})" :
|
||||
$"[{agent?.Name}]: {message.Content}";
|
||||
log += $"\r\n<== MessageId: {message.MessageId}";
|
||||
await _chatHub.Clients.User(_user.Id).SendAsync("OnContentLogGenerated", BuildLog(conversationId, agent?.Name, log));
|
||||
await _chatHub.Clients.User(_user.Id).SendAsync("OnConversationContentLogGenerated", BuildContentLog(conversationId, agent?.Name, log, message));
|
||||
}
|
||||
|
||||
private string BuildLog(string conversationId, string? name, string content)
|
||||
public override async Task OnResponseGenerated(RoleDialogModel message)
|
||||
{
|
||||
var log = new StreamingLogModel
|
||||
var conv = _services.GetRequiredService<IConversationService>();
|
||||
var state = _services.GetRequiredService<IConversationStateService>();
|
||||
|
||||
await _chatHub.Clients.User(_user.Id).SendAsync("OnConversateStateLogGenerated", BuildStateLog(conv.ConversationId, state.GetStates(), message));
|
||||
}
|
||||
|
||||
private string BuildContentLog(string conversationId, string? name, string content, RoleDialogModel message)
|
||||
{
|
||||
var log = new ConversationContentLogModel
|
||||
{
|
||||
ConversationId = conversationId,
|
||||
MessageId = message.MessageId,
|
||||
Name = name,
|
||||
Role = message.Role,
|
||||
Content = content,
|
||||
CreateTime = DateTime.UtcNow
|
||||
};
|
||||
|
||||
var convSettings = _services.GetRequiredService<ConversationSetting>();
|
||||
if (convSettings.EnableContentLog)
|
||||
{
|
||||
var db = _services.GetRequiredService<IBotSharpRepository>();
|
||||
db.SaveConversationContentLog(log);
|
||||
}
|
||||
|
||||
return JsonSerializer.Serialize(log, _serializerOptions);
|
||||
}
|
||||
|
||||
private string BuildStateLog(string conversationId, Dictionary<string, string> states, RoleDialogModel message)
|
||||
{
|
||||
var log = new ConversationStateLogModel
|
||||
{
|
||||
ConversationId = conversationId,
|
||||
MessageId = message.MessageId,
|
||||
States = states,
|
||||
CreateTime = DateTime.UtcNow
|
||||
};
|
||||
|
||||
var convSettings = _services.GetRequiredService<ConversationSetting>();
|
||||
if (convSettings.EnableStateLog)
|
||||
{
|
||||
var db = _services.GetRequiredService<IBotSharpRepository>();
|
||||
db.SaveConversationStateLog(log);
|
||||
}
|
||||
|
||||
return JsonSerializer.Serialize(log, _serializerOptions);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ public class WebSocketsMiddleware
|
|||
if (request.Path.StartsWithSegments("/chatHub", StringComparison.OrdinalIgnoreCase) &&
|
||||
request.Query.TryGetValue("access_token", out var accessToken))
|
||||
{
|
||||
request.Headers.Add("Authorization", $"Bearer {accessToken}");
|
||||
request.Headers["Authorization"] = $"Bearer {accessToken}";
|
||||
}
|
||||
|
||||
await _next(httpContext);
|
||||
|
|
|
|||
|
|
@ -20,3 +20,4 @@ global using BotSharp.Abstraction.Messaging;
|
|||
global using BotSharp.Abstraction.Conversations.Models;
|
||||
global using BotSharp.Abstraction.Conversations;
|
||||
global using BotSharp.Abstraction.Messaging.Models.RichContent.Template;
|
||||
global using BotSharp.Abstraction.Messaging.Enums;
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
namespace BotSharp.Plugin.MongoStorage.Collections;
|
||||
|
||||
public class ConversationContentLogDocument : MongoBase
|
||||
{
|
||||
public string ConversationId { get; set; }
|
||||
public string MessageId { get; set; }
|
||||
public string? Name { get; set; }
|
||||
public string Role { get; set; }
|
||||
public string Content { get; set; }
|
||||
public DateTime CreateTime { get; set; }
|
||||
}
|
||||
|
|
@ -4,6 +4,7 @@ public class ConversationDocument : MongoBase
|
|||
{
|
||||
public string AgentId { get; set; }
|
||||
public string UserId { get; set; }
|
||||
public string? TaskId { get; set; }
|
||||
public string Title { get; set; }
|
||||
public string Channel { get; set; }
|
||||
public string Status { get; set; }
|
||||
|
|
|
|||
|
|
@ -0,0 +1,9 @@
|
|||
namespace BotSharp.Plugin.MongoStorage.Collections;
|
||||
|
||||
public class ConversationStateLogDocument : MongoBase
|
||||
{
|
||||
public string ConversationId { get; set; }
|
||||
public string MessageId { get; set; }
|
||||
public Dictionary<string, string> States { get; set; }
|
||||
public DateTime CreateTime { get; set; }
|
||||
}
|
||||
|
|
@ -7,6 +7,7 @@ public class AgentLlmConfigMongoElement
|
|||
public string? Provider { get; set; }
|
||||
public string? Model { get; set; }
|
||||
public bool IsInherit { get; set; }
|
||||
public int MaxRecursionDepth { get; set; }
|
||||
|
||||
public static AgentLlmConfigMongoElement? ToMongoElement(AgentLlmConfig? config)
|
||||
{
|
||||
|
|
@ -17,6 +18,7 @@ public class AgentLlmConfigMongoElement
|
|||
Provider = config.Provider,
|
||||
Model = config.Model,
|
||||
IsInherit = config.IsInherit,
|
||||
MaxRecursionDepth = config.MaxRecursionDepth,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -29,6 +31,7 @@ public class AgentLlmConfigMongoElement
|
|||
Provider = config.Provider,
|
||||
Model = config.Model,
|
||||
IsInherit = config.IsInherit,
|
||||
MaxRecursionDepth = config.MaxRecursionDepth,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,14 +28,68 @@ public class MongoDbContext
|
|||
|
||||
private IMongoDatabase Database { get { return _mongoClient.GetDatabase(_mongoDbDatabaseName); } }
|
||||
|
||||
#region Indexes
|
||||
private IMongoCollection<ConversationDocument> CreateConversationIndex()
|
||||
{
|
||||
var collection = Database.GetCollection<ConversationDocument>($"{_collectionPrefix}_Conversations");
|
||||
var indexes = collection.Indexes.List().ToList();
|
||||
var createTimeIndex = indexes.FirstOrDefault(x => x.GetElement("name").ToString().StartsWith("CreatedTime"));
|
||||
if (createTimeIndex == null)
|
||||
{
|
||||
var indexDef = Builders<ConversationDocument>.IndexKeys.Descending(x => x.CreatedTime);
|
||||
collection.Indexes.CreateOne(new CreateIndexModel<ConversationDocument>(indexDef));
|
||||
}
|
||||
return collection;
|
||||
}
|
||||
|
||||
private IMongoCollection<AgentTaskDocument> CreateAgentTaskIndex()
|
||||
{
|
||||
var collection = Database.GetCollection<AgentTaskDocument>($"{_collectionPrefix}_AgentTasks");
|
||||
var indexes = collection.Indexes.List().ToList();
|
||||
var createTimeIndex = indexes.FirstOrDefault(x => x.GetElement("name").ToString().StartsWith("CreatedTime"));
|
||||
if (createTimeIndex == null)
|
||||
{
|
||||
var indexDef = Builders<AgentTaskDocument>.IndexKeys.Descending(x => x.CreatedTime);
|
||||
collection.Indexes.CreateOne(new CreateIndexModel<AgentTaskDocument>(indexDef));
|
||||
}
|
||||
return collection;
|
||||
}
|
||||
|
||||
private IMongoCollection<ConversationContentLogDocument> CreateContentLogIndex()
|
||||
{
|
||||
var collection = Database.GetCollection<ConversationContentLogDocument>($"{_collectionPrefix}_ConversationContentLogs");
|
||||
var indexes = collection.Indexes.List().ToList();
|
||||
var createTimeIndex = indexes.FirstOrDefault(x => x.GetElement("name").ToString().StartsWith("CreateTime"));
|
||||
if (createTimeIndex == null)
|
||||
{
|
||||
var indexDef = Builders<ConversationContentLogDocument>.IndexKeys.Ascending(x => x.CreateTime);
|
||||
collection.Indexes.CreateOne(new CreateIndexModel<ConversationContentLogDocument>(indexDef));
|
||||
}
|
||||
return collection;
|
||||
}
|
||||
|
||||
private IMongoCollection<ConversationStateLogDocument> CreateStateLogIndex()
|
||||
{
|
||||
var collection = Database.GetCollection<ConversationStateLogDocument>($"{_collectionPrefix}_ConversationStateLogs");
|
||||
var indexes = collection.Indexes.List().ToList();
|
||||
var createTimeIndex = indexes.FirstOrDefault(x => x.GetElement("name").ToString().StartsWith("CreateTime"));
|
||||
if (createTimeIndex == null)
|
||||
{
|
||||
var indexDef = Builders<ConversationStateLogDocument>.IndexKeys.Ascending(x => x.CreateTime);
|
||||
collection.Indexes.CreateOne(new CreateIndexModel<ConversationStateLogDocument>(indexDef));
|
||||
}
|
||||
return collection;
|
||||
}
|
||||
#endregion
|
||||
|
||||
public IMongoCollection<AgentDocument> Agents
|
||||
=> Database.GetCollection<AgentDocument>($"{_collectionPrefix}_Agents");
|
||||
|
||||
public IMongoCollection<AgentTaskDocument> AgentTasks
|
||||
=> Database.GetCollection<AgentTaskDocument>($"{_collectionPrefix}_AgentTasks");
|
||||
=> CreateAgentTaskIndex();
|
||||
|
||||
public IMongoCollection<ConversationDocument> Conversations
|
||||
=> Database.GetCollection<ConversationDocument>($"{_collectionPrefix}_Conversations");
|
||||
=> CreateConversationIndex();
|
||||
|
||||
public IMongoCollection<ConversationDialogDocument> ConversationDialogs
|
||||
=> Database.GetCollection<ConversationDialogDocument>($"{_collectionPrefix}_ConversationDialogs");
|
||||
|
|
@ -46,15 +100,21 @@ public class MongoDbContext
|
|||
public IMongoCollection<ExecutionLogDocument> ExectionLogs
|
||||
=> Database.GetCollection<ExecutionLogDocument>($"{_collectionPrefix}_ExecutionLogs");
|
||||
|
||||
public IMongoCollection<LlmCompletionLogDocument> LlmCompletionLogs
|
||||
=> Database.GetCollection<LlmCompletionLogDocument>($"{_collectionPrefix}_LlmCompletionLogs");
|
||||
|
||||
public IMongoCollection<ConversationContentLogDocument> ContentLogs
|
||||
=> CreateContentLogIndex();
|
||||
|
||||
public IMongoCollection<ConversationStateLogDocument> StateLogs
|
||||
=> CreateStateLogIndex();
|
||||
|
||||
public IMongoCollection<UserDocument> Users
|
||||
=> Database.GetCollection<UserDocument>($"{_collectionPrefix}_Users");
|
||||
|
||||
public IMongoCollection<UserAgentDocument> UserAgents
|
||||
=> Database.GetCollection<UserAgentDocument>($"{_collectionPrefix}_UserAgents");
|
||||
|
||||
public IMongoCollection<LlmCompletionLogDocument> LlmCompletionLogs
|
||||
=> Database.GetCollection<LlmCompletionLogDocument>($"{_collectionPrefix}_Llm_Completion_Logs");
|
||||
|
||||
public IMongoCollection<PluginDocument> Plugins
|
||||
=> Database.GetCollection<PluginDocument>($"{_collectionPrefix}_Plugins");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ public partial class MongoRepository
|
|||
UserId = !string.IsNullOrEmpty(conversation.UserId) ? conversation.UserId : string.Empty,
|
||||
Title = conversation.Title,
|
||||
Channel = conversation.Channel,
|
||||
TaskId = conversation.TaskId,
|
||||
Status = conversation.Status,
|
||||
CreatedTime = DateTime.UtcNow,
|
||||
UpdatedTime = DateTime.UtcNow,
|
||||
|
|
@ -62,14 +63,20 @@ public partial class MongoRepository
|
|||
var filterSates = Builders<ConversationStateDocument>.Filter.Eq(x => x.ConversationId, conversationId);
|
||||
var filterExeLog = Builders<ExecutionLogDocument>.Filter.Eq(x => x.ConversationId, conversationId);
|
||||
var filterPromptLog = Builders<LlmCompletionLogDocument>.Filter.Eq(x => x.ConversationId, conversationId);
|
||||
var filterContentLog = Builders<ConversationContentLogDocument>.Filter.Eq(x => x.ConversationId, conversationId);
|
||||
var filterStateLog = Builders<ConversationStateLogDocument>.Filter.Eq(x => x.ConversationId, conversationId);
|
||||
|
||||
var exeLogDeleted = _dc.ExectionLogs.DeleteMany(filterExeLog);
|
||||
var promptLogDeleted = _dc.LlmCompletionLogs.DeleteMany(filterPromptLog);
|
||||
var contentLogDeleted = _dc.ContentLogs.DeleteMany(filterContentLog);
|
||||
var stateLogDeleted = _dc.StateLogs.DeleteMany(filterStateLog);
|
||||
var statesDeleted = _dc.ConversationStates.DeleteMany(filterSates);
|
||||
var dialogDeleted = _dc.ConversationDialogs.DeleteMany(filterDialog);
|
||||
var convDeleted = _dc.Conversations.DeleteMany(filterConv);
|
||||
|
||||
return convDeleted.DeletedCount > 0 || dialogDeleted.DeletedCount > 0 || statesDeleted.DeletedCount > 0
|
||||
|| exeLogDeleted.DeletedCount > 0 || promptLogDeleted.DeletedCount > 0;
|
||||
|| exeLogDeleted.DeletedCount > 0 || promptLogDeleted.DeletedCount > 0
|
||||
|| contentLogDeleted.DeletedCount > 0 || stateLogDeleted.DeletedCount > 0;
|
||||
}
|
||||
|
||||
public List<DialogElement> GetConversationDialogs(string conversationId)
|
||||
|
|
@ -215,6 +222,7 @@ public partial class MongoRepository
|
|||
if (!string.IsNullOrEmpty(filter.Status)) filters.Add(builder.Eq(x => x.Status, filter.Status));
|
||||
if (!string.IsNullOrEmpty(filter.Channel)) filters.Add(builder.Eq(x => x.Channel, filter.Channel));
|
||||
if (!string.IsNullOrEmpty(filter.UserId)) filters.Add(builder.Eq(x => x.UserId, filter.UserId));
|
||||
if (!string.IsNullOrEmpty(filter.TaskId)) filters.Add(builder.Eq(x => x.TaskId, filter.TaskId));
|
||||
|
||||
var filterDef = builder.And(filters);
|
||||
var sortDef = Builders<ConversationDocument>.Sort.Descending(x => x.CreatedTime);
|
||||
|
|
@ -230,6 +238,7 @@ public partial class MongoRepository
|
|||
Id = convId,
|
||||
AgentId = conv.AgentId.ToString(),
|
||||
UserId = conv.UserId.ToString(),
|
||||
TaskId = conv.TaskId,
|
||||
Title = conv.Title,
|
||||
Channel = conv.Channel,
|
||||
Status = conv.Status,
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
using BotSharp.Abstraction.Conversations.Models;
|
||||
using BotSharp.Abstraction.Loggers.Models;
|
||||
using BotSharp.Plugin.MongoStorage.Collections;
|
||||
using BotSharp.Plugin.MongoStorage.Models;
|
||||
|
||||
|
|
@ -58,4 +58,82 @@ public partial class MongoRepository
|
|||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Conversation Content Log
|
||||
public void SaveConversationContentLog(ConversationContentLogModel log)
|
||||
{
|
||||
if (log == null) return;
|
||||
|
||||
var conversationId = log.ConversationId.IfNullOrEmptyAs(Guid.NewGuid().ToString());
|
||||
var messageId = log.MessageId.IfNullOrEmptyAs(Guid.NewGuid().ToString());
|
||||
|
||||
var logDoc = new ConversationContentLogDocument
|
||||
{
|
||||
ConversationId = conversationId,
|
||||
MessageId = messageId,
|
||||
Name = log.Name,
|
||||
Role = log.Role,
|
||||
Content = log.Content,
|
||||
CreateTime = log.CreateTime
|
||||
};
|
||||
|
||||
_dc.ContentLogs.InsertOne(logDoc);
|
||||
}
|
||||
|
||||
public List<ConversationContentLogModel> GetConversationContentLogs(string conversationId)
|
||||
{
|
||||
var logs = _dc.ContentLogs
|
||||
.AsQueryable()
|
||||
.Where(x => x.ConversationId == conversationId)
|
||||
.Select(x => new ConversationContentLogModel
|
||||
{
|
||||
ConversationId = x.ConversationId,
|
||||
MessageId = x.MessageId,
|
||||
Name = x.Name,
|
||||
Role = x.Role,
|
||||
Content = x.Content,
|
||||
CreateTime = x.CreateTime
|
||||
})
|
||||
.OrderBy(x => x.CreateTime)
|
||||
.ToList();
|
||||
return logs;
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Conversation State Log
|
||||
public void SaveConversationStateLog(ConversationStateLogModel log)
|
||||
{
|
||||
if (log == null) return;
|
||||
|
||||
var conversationId = log.ConversationId.IfNullOrEmptyAs(Guid.NewGuid().ToString());
|
||||
var messageId = log.MessageId.IfNullOrEmptyAs(Guid.NewGuid().ToString());
|
||||
|
||||
var logDoc = new ConversationStateLogDocument
|
||||
{
|
||||
ConversationId = conversationId,
|
||||
MessageId = messageId,
|
||||
States = log.States,
|
||||
CreateTime = log.CreateTime
|
||||
};
|
||||
|
||||
_dc.StateLogs.InsertOne(logDoc);
|
||||
}
|
||||
|
||||
public List<ConversationStateLogModel> GetConversationStateLogs(string conversationId)
|
||||
{
|
||||
var logs = _dc.StateLogs
|
||||
.AsQueryable()
|
||||
.Where(x => x.ConversationId == conversationId)
|
||||
.Select(x => new ConversationStateLogModel
|
||||
{
|
||||
ConversationId = x.ConversationId,
|
||||
MessageId = x.MessageId,
|
||||
States = x.States,
|
||||
CreateTime = x.CreateTime
|
||||
})
|
||||
.OrderBy(x => x.CreateTime)
|
||||
.ToList();
|
||||
return logs;
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,8 +2,9 @@ namespace BotSharp.Plugin.WebDriver.Drivers;
|
|||
|
||||
public interface IWebBrowser
|
||||
{
|
||||
Task LaunchBrowser(string? url);
|
||||
Task<string> ScreenshotAsync(string path);
|
||||
Task<bool> LaunchBrowser(string conversationId, string? url);
|
||||
Task<string> ScreenshotAsync(string conversationId, string path);
|
||||
Task<bool> ScrollPageAsync(BrowserActionParams actionParams);
|
||||
Task<bool> InputUserText(BrowserActionParams actionParams);
|
||||
Task<bool> InputUserPassword(BrowserActionParams actionParams);
|
||||
Task<bool> ClickButton(BrowserActionParams actionParams);
|
||||
|
|
@ -11,8 +12,8 @@ public interface IWebBrowser
|
|||
Task<bool> ChangeListValue(BrowserActionParams actionParams);
|
||||
Task<bool> CheckRadioButton(BrowserActionParams actionParams);
|
||||
Task<bool> ChangeCheckbox(BrowserActionParams actionParams);
|
||||
Task<bool> GoToPage(BrowserActionParams actionParams);
|
||||
Task<bool> GoToPage(string conversationId, string url);
|
||||
Task<string> ExtractData(BrowserActionParams actionParams);
|
||||
Task<T> EvaluateScript<T>(string script);
|
||||
Task CloseBrowser();
|
||||
Task<T> EvaluateScript<T>(string conversationId, string script);
|
||||
Task CloseBrowser(string conversationId);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,66 +5,89 @@ namespace BotSharp.Plugin.WebDriver.Drivers.PlaywrightDriver;
|
|||
public class PlaywrightInstance : IDisposable
|
||||
{
|
||||
IPlaywright _playwright;
|
||||
IBrowserContext _context;
|
||||
Dictionary<string, IBrowserContext> _contexts = new Dictionary<string, IBrowserContext>();
|
||||
|
||||
public IBrowserContext Context => _context;
|
||||
public IPage Page
|
||||
public IPage GetPage(string id)
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_context == null)
|
||||
{
|
||||
InitInstance().Wait();
|
||||
}
|
||||
return _context.Pages.LastOrDefault();
|
||||
}
|
||||
InitInstance(id).Wait();
|
||||
return _contexts[id].Pages.LastOrDefault();
|
||||
}
|
||||
|
||||
public async Task InitInstance()
|
||||
public async Task InitInstance(string id)
|
||||
{
|
||||
if (_playwright == null)
|
||||
{
|
||||
_playwright = await Playwright.CreateAsync();
|
||||
}
|
||||
await InitContext(id);
|
||||
}
|
||||
|
||||
if (_context == null)
|
||||
public async Task InitContext(string id)
|
||||
{
|
||||
if (_contexts.ContainsKey(id))
|
||||
return;
|
||||
|
||||
string tempFolderPath = $"{Path.GetTempPath()}\\playwright\\{id}";
|
||||
_contexts[id] = await _playwright.Chromium.LaunchPersistentContextAsync(tempFolderPath, new BrowserTypeLaunchPersistentContextOptions
|
||||
{
|
||||
string tempFolderPath = $"{Path.GetTempPath()}\\playwright\\{Guid.NewGuid()}";
|
||||
_context = await _playwright.Chromium.LaunchPersistentContextAsync(tempFolderPath, new BrowserTypeLaunchPersistentContextOptions
|
||||
Headless = true,
|
||||
Channel = "chrome",
|
||||
IgnoreDefaultArgs = new[]
|
||||
{
|
||||
Headless = false,
|
||||
Channel = "chrome",
|
||||
IgnoreDefaultArgs = new[]
|
||||
{
|
||||
"--disable-infobars"
|
||||
},
|
||||
Args = new[]
|
||||
{
|
||||
Args = new[]
|
||||
{
|
||||
"--disable-infobars",
|
||||
// "--start-maximized"
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
_context.Page += async (sender, e) =>
|
||||
_contexts[id].Page += async (sender, e) =>
|
||||
{
|
||||
e.Close += async (sender, e) =>
|
||||
{
|
||||
e.Close += async (sender, e) =>
|
||||
{
|
||||
Serilog.Log.Information($"Page is closed: {e.Url}");
|
||||
};
|
||||
Serilog.Log.Information($"New page is created: {e.Url}");
|
||||
await e.SetViewportSizeAsync(1280, 800);
|
||||
Serilog.Log.Information($"Page is closed: {e.Url}");
|
||||
};
|
||||
Serilog.Log.Information($"New page is created: {e.Url}");
|
||||
await e.SetViewportSizeAsync(1280, 800);
|
||||
};
|
||||
|
||||
_context.Close += async (sender, e) =>
|
||||
{
|
||||
Serilog.Log.Warning($"Playwright browser context is closed");
|
||||
_context = null;
|
||||
};
|
||||
_contexts[id].Close += async (sender, e) =>
|
||||
{
|
||||
Serilog.Log.Warning($"Playwright browser context is closed");
|
||||
_contexts.Remove(id);
|
||||
};
|
||||
}
|
||||
|
||||
public async Task<IPage> NewPage(string id)
|
||||
{
|
||||
await InitContext(id);
|
||||
return await _contexts[id].NewPageAsync();
|
||||
}
|
||||
|
||||
public async Task Wait(string id)
|
||||
{
|
||||
if (_contexts.ContainsKey(id))
|
||||
{
|
||||
var page = _contexts[id].Pages.Last();
|
||||
await page.WaitForLoadStateAsync(LoadState.DOMContentLoaded);
|
||||
await page.WaitForLoadStateAsync(LoadState.NetworkIdle);
|
||||
}
|
||||
await Task.Delay(100);
|
||||
}
|
||||
|
||||
public async Task Close(string id)
|
||||
{
|
||||
if (_contexts.ContainsKey(id))
|
||||
{
|
||||
await _contexts[id].CloseAsync();
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_contexts.Clear();
|
||||
_playwright.Dispose();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,8 +7,7 @@ public partial class PlaywrightWebDriver
|
|||
{
|
||||
public async Task<bool> ChangeCheckbox(BrowserActionParams actionParams)
|
||||
{
|
||||
await _instance.Page.WaitForLoadStateAsync(LoadState.DOMContentLoaded);
|
||||
await _instance.Page.WaitForLoadStateAsync(LoadState.NetworkIdle);
|
||||
await _instance.Wait(actionParams.ConversationId);
|
||||
|
||||
// Retrieve the page raw html and infer the element path
|
||||
var regexExpression = actionParams.Context.MatchRule.ToLower() switch
|
||||
|
|
@ -19,7 +18,7 @@ public partial class PlaywrightWebDriver
|
|||
_ => $"^{actionParams.Context.ElementText}$"
|
||||
};
|
||||
var regex = new Regex(regexExpression, RegexOptions.IgnoreCase);
|
||||
var elements = _instance.Page.GetByText(regex);
|
||||
var elements = _instance.GetPage(actionParams.ConversationId).GetByText(regex);
|
||||
var count = await elements.CountAsync();
|
||||
|
||||
if (count == 0)
|
||||
|
|
@ -51,7 +50,7 @@ public partial class PlaywrightWebDriver
|
|||
}
|
||||
else
|
||||
{
|
||||
elements = _instance.Page.Locator($"#{id}");
|
||||
elements = _instance.GetPage(actionParams.ConversationId).Locator($"#{id}");
|
||||
}
|
||||
count = await elements.CountAsync();
|
||||
|
||||
|
|
|
|||
|
|
@ -6,11 +6,10 @@ public partial class PlaywrightWebDriver
|
|||
{
|
||||
public async Task<bool> ChangeListValue(BrowserActionParams actionParams)
|
||||
{
|
||||
await _instance.Page.WaitForLoadStateAsync(LoadState.DOMContentLoaded);
|
||||
await _instance.Page.WaitForLoadStateAsync(LoadState.NetworkIdle);
|
||||
await _instance.Wait(actionParams.ConversationId);
|
||||
|
||||
// Retrieve the page raw html and infer the element path
|
||||
var body = await _instance.Page.QuerySelectorAsync("body");
|
||||
var body = await _instance.GetPage(actionParams.ConversationId).QuerySelectorAsync("body");
|
||||
|
||||
var str = new List<string>();
|
||||
var inputs = await body.QuerySelectorAllAsync("select");
|
||||
|
|
@ -63,7 +62,7 @@ public partial class PlaywrightWebDriver
|
|||
string.Join("", str),
|
||||
actionParams.Context.ElementName,
|
||||
actionParams.MessageId);
|
||||
ILocator element = Locator(htmlElementContextOut);
|
||||
ILocator element = Locator(actionParams.ConversationId, htmlElementContextOut);
|
||||
|
||||
try
|
||||
{
|
||||
|
|
@ -72,13 +71,15 @@ public partial class PlaywrightWebDriver
|
|||
if (!isVisible)
|
||||
{
|
||||
// Select the element you want to make visible (replace with your own selector)
|
||||
var control = await _instance.Page.QuerySelectorAsync($"#{htmlElementContextOut.ElementId}");
|
||||
var control = await _instance.GetPage(actionParams.ConversationId)
|
||||
.QuerySelectorAsync($"#{htmlElementContextOut.ElementId}");
|
||||
|
||||
// Show the element by modifying its CSS styles
|
||||
await _instance.Page.EvaluateAsync(@"(element) => {
|
||||
element.style.display = 'block';
|
||||
element.style.visibility = 'visible';
|
||||
}", control);
|
||||
await _instance.GetPage(actionParams.ConversationId)
|
||||
.EvaluateAsync(@"(element) => {
|
||||
element.style.display = 'block';
|
||||
element.style.visibility = 'visible';
|
||||
}", control);
|
||||
}
|
||||
|
||||
await element.FocusAsync();
|
||||
|
|
@ -92,10 +93,11 @@ public partial class PlaywrightWebDriver
|
|||
if (!isVisible)
|
||||
{
|
||||
// Select the element you want to make visible (replace with your own selector)
|
||||
var control = await _instance.Page.QuerySelectorAsync($"#{htmlElementContextOut.ElementId}");
|
||||
var control = await _instance.GetPage(actionParams.ConversationId)
|
||||
.QuerySelectorAsync($"#{htmlElementContextOut.ElementId}");
|
||||
|
||||
// Show the element by modifying its CSS styles
|
||||
await _instance.Page.EvaluateAsync(@"(element) => {
|
||||
await _instance.GetPage(actionParams.ConversationId).EvaluateAsync(@"(element) => {
|
||||
element.style.display = 'none';
|
||||
element.style.visibility = 'hidden';
|
||||
}", control);
|
||||
|
|
|
|||
|
|
@ -7,8 +7,7 @@ public partial class PlaywrightWebDriver
|
|||
{
|
||||
public async Task<bool> CheckRadioButton(BrowserActionParams actionParams)
|
||||
{
|
||||
await _instance.Page.WaitForLoadStateAsync(LoadState.DOMContentLoaded);
|
||||
await _instance.Page.WaitForLoadStateAsync(LoadState.NetworkIdle);
|
||||
await _instance.Wait(actionParams.ConversationId);
|
||||
|
||||
// Retrieve the page raw html and infer the element path
|
||||
var regexExpression = actionParams.Context.MatchRule.ToLower() switch
|
||||
|
|
@ -19,7 +18,7 @@ public partial class PlaywrightWebDriver
|
|||
_ => $"^{actionParams.Context.ElementText}$"
|
||||
};
|
||||
var regex = new Regex(regexExpression, RegexOptions.IgnoreCase);
|
||||
var elements = _instance.Page.GetByText(regex);
|
||||
var elements = _instance.GetPage(actionParams.ConversationId).GetByText(regex);
|
||||
var count = await elements.CountAsync();
|
||||
|
||||
if (count == 0)
|
||||
|
|
|
|||
|
|
@ -6,30 +6,30 @@ public partial class PlaywrightWebDriver
|
|||
{
|
||||
public async Task<bool> ClickButton(BrowserActionParams actionParams)
|
||||
{
|
||||
await _instance.Page.WaitForLoadStateAsync(LoadState.DOMContentLoaded);
|
||||
await _instance.Page.WaitForLoadStateAsync(LoadState.NetworkIdle);
|
||||
|
||||
await Task.Delay(100);
|
||||
await _instance.Wait(actionParams.ConversationId);
|
||||
|
||||
// Find by text exactly match
|
||||
var elements = _instance.Page.GetByRole(AriaRole.Button, new PageGetByRoleOptions
|
||||
{
|
||||
Name = actionParams.Context.ElementName
|
||||
});
|
||||
var elements = _instance.GetPage(actionParams.ConversationId)
|
||||
.GetByRole(AriaRole.Button, new PageGetByRoleOptions
|
||||
{
|
||||
Name = actionParams.Context.ElementName
|
||||
});
|
||||
var count = await elements.CountAsync();
|
||||
|
||||
if (count == 0)
|
||||
{
|
||||
elements = _instance.Page.GetByRole(AriaRole.Link, new PageGetByRoleOptions
|
||||
{
|
||||
Name = actionParams.Context.ElementName
|
||||
});
|
||||
elements = _instance.GetPage(actionParams.ConversationId)
|
||||
.GetByRole(AriaRole.Link, new PageGetByRoleOptions
|
||||
{
|
||||
Name = actionParams.Context.ElementName
|
||||
});
|
||||
count = await elements.CountAsync();
|
||||
}
|
||||
|
||||
if (count == 0)
|
||||
{
|
||||
elements = _instance.Page.GetByText(actionParams.Context.ElementName);
|
||||
elements = _instance.GetPage(actionParams.ConversationId)
|
||||
.GetByText(actionParams.Context.ElementName);
|
||||
count = await elements.CountAsync();
|
||||
}
|
||||
|
||||
|
|
@ -37,12 +37,12 @@ public partial class PlaywrightWebDriver
|
|||
{
|
||||
// Infer element if not found
|
||||
var driverService = _services.GetRequiredService<WebDriverService>();
|
||||
var html = await FilteredButtonHtml();
|
||||
var html = await FilteredButtonHtml(actionParams.ConversationId);
|
||||
var htmlElementContextOut = await driverService.InferElement(actionParams.Agent,
|
||||
html,
|
||||
actionParams.Context.ElementName,
|
||||
actionParams.MessageId);
|
||||
elements = Locator(htmlElementContextOut);
|
||||
elements = Locator(actionParams.ConversationId, htmlElementContextOut);
|
||||
|
||||
if (elements == null)
|
||||
{
|
||||
|
|
@ -52,10 +52,9 @@ public partial class PlaywrightWebDriver
|
|||
|
||||
try
|
||||
{
|
||||
await elements.HoverAsync();
|
||||
await elements.ClickAsync();
|
||||
await _instance.Wait(actionParams.ConversationId);
|
||||
|
||||
await Task.Delay(300);
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
|
@ -65,12 +64,12 @@ public partial class PlaywrightWebDriver
|
|||
return false;
|
||||
}
|
||||
|
||||
private async Task<string> FilteredButtonHtml()
|
||||
private async Task<string> FilteredButtonHtml(string conversationId)
|
||||
{
|
||||
var driverService = _services.GetRequiredService<WebDriverService>();
|
||||
|
||||
// Retrieve the page raw html and infer the element path
|
||||
var body = await _instance.Page.QuerySelectorAsync("body");
|
||||
var body = await _instance.GetPage(conversationId).QuerySelectorAsync("body");
|
||||
|
||||
var str = new List<string>();
|
||||
/*var anchors = await body.QuerySelectorAllAsync("a");
|
||||
|
|
|
|||
|
|
@ -7,26 +7,39 @@ public partial class PlaywrightWebDriver
|
|||
{
|
||||
public async Task<bool> ClickElement(BrowserActionParams actionParams)
|
||||
{
|
||||
await _instance.Page.WaitForLoadStateAsync(LoadState.DOMContentLoaded);
|
||||
await _instance.Page.WaitForLoadStateAsync(LoadState.NetworkIdle);
|
||||
await _instance.Wait(actionParams.ConversationId);
|
||||
|
||||
var page = _instance.GetPage(actionParams.ConversationId);
|
||||
ILocator locator = default;
|
||||
int count = 0;
|
||||
|
||||
// Retrieve the page raw html and infer the element path
|
||||
var regexExpression = actionParams.Context.MatchRule.ToLower() switch
|
||||
if (!string.IsNullOrEmpty(actionParams.Context.ElementText))
|
||||
{
|
||||
"startwith" => $"^{actionParams.Context.ElementText}",
|
||||
"endwith" => $"{actionParams.Context.ElementText}$",
|
||||
"contains" => $"{actionParams.Context.ElementText}",
|
||||
_ => $"^{actionParams.Context.ElementText}$"
|
||||
};
|
||||
var regex = new Regex(regexExpression, RegexOptions.IgnoreCase);
|
||||
var elements = _instance.Page.GetByText(regex);
|
||||
var count = await elements.CountAsync();
|
||||
var regexExpression = actionParams.Context.MatchRule.ToLower() switch
|
||||
{
|
||||
"startwith" => $"^{actionParams.Context.ElementText}",
|
||||
"endwith" => $"{actionParams.Context.ElementText}$",
|
||||
"contains" => $"{actionParams.Context.ElementText}",
|
||||
_ => $"^{actionParams.Context.ElementText}$"
|
||||
};
|
||||
var regex = new Regex(regexExpression, RegexOptions.IgnoreCase);
|
||||
locator = page.GetByText(regex);
|
||||
count = await locator.CountAsync();
|
||||
|
||||
// try placeholder
|
||||
if (count == 0)
|
||||
// try placeholder
|
||||
if (count == 0)
|
||||
{
|
||||
locator = page.GetByPlaceholder(regex);
|
||||
count = await locator.CountAsync();
|
||||
}
|
||||
}
|
||||
|
||||
// try attribute
|
||||
if (count == 0 && !string.IsNullOrEmpty(actionParams.Context.AttributeName))
|
||||
{
|
||||
elements = _instance.Page.GetByPlaceholder(regex);
|
||||
count = await elements.CountAsync();
|
||||
locator = page.Locator($"[{actionParams.Context.AttributeName}='{actionParams.Context.AttributeValue}']");
|
||||
count = await locator.CountAsync();
|
||||
}
|
||||
|
||||
if (count == 0)
|
||||
|
|
@ -35,20 +48,18 @@ public partial class PlaywrightWebDriver
|
|||
}
|
||||
else if (count == 1)
|
||||
{
|
||||
// var tagName = await elements.EvaluateAsync<string>("el => el.tagName");
|
||||
|
||||
await elements.HoverAsync();
|
||||
await elements.ClickAsync();
|
||||
// var tagName = await locator.EvaluateAsync<string>("el => el.tagName");
|
||||
await locator.ClickAsync();
|
||||
|
||||
// Triggered ajax
|
||||
await _instance.Page.WaitForLoadStateAsync(LoadState.NetworkIdle);
|
||||
await _instance.Wait(actionParams.ConversationId);
|
||||
|
||||
return true;
|
||||
}
|
||||
else if (count > 1)
|
||||
{
|
||||
_logger.LogWarning($"Multiple elements are found by keyword {actionParams.Context.ElementText}");
|
||||
var all = await elements.AllAsync();
|
||||
var all = await locator.AllAsync();
|
||||
foreach (var element in all)
|
||||
{
|
||||
var content = await element.TextContentAsync();
|
||||
|
|
|
|||
|
|
@ -1,13 +1,9 @@
|
|||
|
||||
namespace BotSharp.Plugin.WebDriver.Drivers.PlaywrightDriver;
|
||||
|
||||
public partial class PlaywrightWebDriver
|
||||
{
|
||||
public async Task CloseBrowser()
|
||||
public async Task CloseBrowser(string conversationId)
|
||||
{
|
||||
if (_instance.Context != null)
|
||||
{
|
||||
await _instance.Context.CloseAsync();
|
||||
}
|
||||
await _instance.Close(conversationId);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,11 +2,10 @@ namespace BotSharp.Plugin.WebDriver.Drivers.PlaywrightDriver;
|
|||
|
||||
public partial class PlaywrightWebDriver
|
||||
{
|
||||
public async Task<T> EvaluateScript<T>(string script)
|
||||
public async Task<T> EvaluateScript<T>(string conversationId, string script)
|
||||
{
|
||||
await _instance.Page.WaitForLoadStateAsync(LoadState.DOMContentLoaded);
|
||||
await _instance.Page.WaitForLoadStateAsync(LoadState.NetworkIdle);
|
||||
await _instance.Wait(conversationId);
|
||||
|
||||
return await _instance.Page.EvaluateAsync<T>(script);
|
||||
return await _instance.GetPage(conversationId).EvaluateAsync<T>(script);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,13 +4,12 @@ public partial class PlaywrightWebDriver
|
|||
{
|
||||
public async Task<string> ExtractData(BrowserActionParams actionParams)
|
||||
{
|
||||
await _instance.Page.WaitForLoadStateAsync(LoadState.DOMContentLoaded);
|
||||
await _instance.Page.WaitForLoadStateAsync(LoadState.NetworkIdle);
|
||||
await _instance.Wait(actionParams.ConversationId);
|
||||
|
||||
await Task.Delay(3000);
|
||||
|
||||
// Retrieve the page raw html and infer the element path
|
||||
var body = await _instance.Page.QuerySelectorAsync("body");
|
||||
var body = await _instance.GetPage(actionParams.ConversationId).QuerySelectorAsync("body");
|
||||
var content = await body.InnerTextAsync();
|
||||
|
||||
var driverService = _services.GetRequiredService<WebDriverService>();
|
||||
|
|
|
|||
|
|
@ -1,11 +1,24 @@
|
|||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace BotSharp.Plugin.WebDriver.Drivers.PlaywrightDriver;
|
||||
|
||||
public partial class PlaywrightWebDriver
|
||||
{
|
||||
public async Task<bool> GoToPage(BrowserActionParams actionParams)
|
||||
public async Task<bool> GoToPage(string conversationId, string url)
|
||||
{
|
||||
await _instance.Page.GotoAsync(actionParams.Context.Url);
|
||||
await _instance.Page.WaitForLoadStateAsync(LoadState.DOMContentLoaded);
|
||||
return true;
|
||||
try
|
||||
{
|
||||
var response = await _instance.GetPage(conversationId).GotoAsync(url);
|
||||
await _instance.GetPage(conversationId).WaitForLoadStateAsync(LoadState.DOMContentLoaded);
|
||||
await _instance.GetPage(conversationId).WaitForLoadStateAsync(LoadState.NetworkIdle);
|
||||
|
||||
return response.Status == 200;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex.Message);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,25 +6,25 @@ public partial class PlaywrightWebDriver
|
|||
{
|
||||
public async Task<bool> InputUserPassword(BrowserActionParams actionParams)
|
||||
{
|
||||
await _instance.Page.WaitForLoadStateAsync(LoadState.DOMContentLoaded);
|
||||
await _instance.Wait(actionParams.ConversationId);
|
||||
|
||||
// Retrieve the page raw html and infer the element path
|
||||
var body = await _instance.Page.QuerySelectorAsync("body");
|
||||
var body = await _instance.GetPage(actionParams.ConversationId)
|
||||
.QuerySelectorAsync("body");
|
||||
|
||||
var inputs = await body.QuerySelectorAllAsync("input");
|
||||
var password = inputs.FirstOrDefault(x => x.GetAttributeAsync("type").Result == "password");
|
||||
|
||||
if (password == null)
|
||||
{
|
||||
throw new Exception($"Can't locate the web element {actionParams.Context.ElementName}.");
|
||||
_logger.LogError($"Can't locate the password element by '{actionParams.Context.ElementName}'");
|
||||
return false;
|
||||
}
|
||||
|
||||
var config = _services.GetRequiredService<IConfiguration>();
|
||||
try
|
||||
{
|
||||
var key = actionParams.Context.Password.Replace("@", "").Replace(".", ":");
|
||||
var value = config.GetValue<string>(key);
|
||||
await password.FillAsync(value);
|
||||
await password.FillAsync(actionParams.Context.Password);
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
|
|
|||
|
|
@ -6,49 +6,59 @@ public partial class PlaywrightWebDriver
|
|||
{
|
||||
public async Task<bool> InputUserText(BrowserActionParams actionParams)
|
||||
{
|
||||
await _instance.Page.WaitForLoadStateAsync(LoadState.DOMContentLoaded);
|
||||
await _instance.Page.WaitForLoadStateAsync(LoadState.NetworkIdle);
|
||||
await _instance.Wait(actionParams.ConversationId);
|
||||
|
||||
var page = _instance.GetPage(actionParams.ConversationId);
|
||||
ILocator locator = default;
|
||||
int count = 0;
|
||||
|
||||
// try attribute
|
||||
if (count == 0 && !string.IsNullOrEmpty(actionParams.Context.AttributeName))
|
||||
{
|
||||
locator = page.Locator($"[{actionParams.Context.AttributeName}='{actionParams.Context.AttributeValue}']");
|
||||
count = await locator.CountAsync();
|
||||
}
|
||||
|
||||
// Find by text exactly match
|
||||
var elements = _instance.Page.GetByRole(AriaRole.Textbox, new PageGetByRoleOptions
|
||||
{
|
||||
Name = actionParams.Context.ElementText
|
||||
});
|
||||
var count = await elements.CountAsync();
|
||||
if (count == 0)
|
||||
{
|
||||
elements = _instance.Page.GetByPlaceholder(actionParams.Context.ElementText);
|
||||
count = await elements.CountAsync();
|
||||
locator = page.GetByRole(AriaRole.Textbox, new PageGetByRoleOptions
|
||||
{
|
||||
Name = actionParams.Context.ElementText
|
||||
});
|
||||
count = await locator.CountAsync();
|
||||
}
|
||||
|
||||
if (count == 0)
|
||||
{
|
||||
locator = page.GetByPlaceholder(actionParams.Context.ElementText);
|
||||
count = await locator.CountAsync();
|
||||
}
|
||||
|
||||
if (count == 0)
|
||||
{
|
||||
var driverService = _services.GetRequiredService<WebDriverService>();
|
||||
var html = await FilteredInputHtml();
|
||||
var html = await FilteredInputHtml(actionParams.ConversationId);
|
||||
var htmlElementContextOut = await driverService.InferElement(actionParams.Agent,
|
||||
html,
|
||||
actionParams.Context.ElementText,
|
||||
actionParams.MessageId);
|
||||
elements = Locator(htmlElementContextOut);
|
||||
count = await elements.CountAsync();
|
||||
locator = Locator(actionParams.ConversationId, htmlElementContextOut);
|
||||
count = await locator.CountAsync();
|
||||
}
|
||||
|
||||
if (count == 0)
|
||||
{
|
||||
|
||||
}
|
||||
else if (count == 1)
|
||||
|
||||
if (count == 1)
|
||||
{
|
||||
try
|
||||
{
|
||||
await elements.FillAsync(actionParams.Context.InputText);
|
||||
await locator.FillAsync(actionParams.Context.InputText);
|
||||
if (actionParams.Context.PressEnter.HasValue && actionParams.Context.PressEnter.Value)
|
||||
{
|
||||
await elements.PressAsync("Enter");
|
||||
await locator.PressAsync("Enter");
|
||||
}
|
||||
|
||||
// Triggered ajax
|
||||
await _instance.Page.WaitForLoadStateAsync(LoadState.NetworkIdle);
|
||||
await _instance.Wait(actionParams.ConversationId);
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
|
@ -60,12 +70,12 @@ public partial class PlaywrightWebDriver
|
|||
return false;
|
||||
}
|
||||
|
||||
private async Task<string> FilteredInputHtml()
|
||||
private async Task<string> FilteredInputHtml(string conversationId)
|
||||
{
|
||||
var driverService = _services.GetRequiredService<WebDriverService>();
|
||||
|
||||
// Retrieve the page raw html and infer the element path
|
||||
var body = await _instance.Page.QuerySelectorAsync("body");
|
||||
var body = await _instance.GetPage(conversationId).QuerySelectorAsync("body");
|
||||
|
||||
var str = new List<string>();
|
||||
var inputs = await body.QuerySelectorAllAsync("input");
|
||||
|
|
|
|||
|
|
@ -1,27 +1,36 @@
|
|||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace BotSharp.Plugin.WebDriver.Drivers.PlaywrightDriver;
|
||||
|
||||
public partial class PlaywrightWebDriver
|
||||
{
|
||||
public async Task LaunchBrowser(string? url)
|
||||
public async Task<bool> LaunchBrowser(string conversationId, string? url)
|
||||
{
|
||||
await _instance.InitInstance();
|
||||
await _instance.InitInstance(conversationId);
|
||||
|
||||
if (!string.IsNullOrEmpty(url))
|
||||
{
|
||||
var page = _instance.Context.Pages.LastOrDefault();
|
||||
if (page == null)
|
||||
{
|
||||
page = await _instance.Context.NewPageAsync();
|
||||
}
|
||||
var page = await _instance.NewPage(conversationId);
|
||||
|
||||
if (!string.IsNullOrEmpty(url))
|
||||
{
|
||||
var response = await page.GotoAsync(url, new PageGotoOptions
|
||||
try
|
||||
{
|
||||
Timeout = 15 * 1000
|
||||
});
|
||||
await page.WaitForLoadStateAsync(LoadState.DOMContentLoaded);
|
||||
var response = await page.GotoAsync(url, new PageGotoOptions
|
||||
{
|
||||
Timeout = 15 * 1000
|
||||
});
|
||||
await page.WaitForLoadStateAsync(LoadState.DOMContentLoaded);
|
||||
return response.Status == 200;
|
||||
}
|
||||
catch(Exception ex)
|
||||
{
|
||||
_logger.LogError(ex.Message);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,11 +3,15 @@ namespace BotSharp.Plugin.WebDriver.Drivers.PlaywrightDriver;
|
|||
|
||||
public partial class PlaywrightWebDriver
|
||||
{
|
||||
public async Task<string> ScreenshotAsync(string path)
|
||||
public async Task<string> ScreenshotAsync(string conversationId, string path)
|
||||
{
|
||||
var bytes = await _instance.Page.ScreenshotAsync(new PageScreenshotOptions
|
||||
await _instance.Wait(conversationId);
|
||||
var page = _instance.GetPage(conversationId);
|
||||
|
||||
await Task.Delay(500);
|
||||
var bytes = await page.ScreenshotAsync(new PageScreenshotOptions
|
||||
{
|
||||
Path = path,
|
||||
Path = path
|
||||
});
|
||||
|
||||
return "data:image/png;base64," + Convert.ToBase64String(bytes);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,23 @@
|
|||
|
||||
namespace BotSharp.Plugin.WebDriver.Drivers.PlaywrightDriver;
|
||||
|
||||
public partial class PlaywrightWebDriver
|
||||
{
|
||||
public async Task<bool> ScrollPageAsync(BrowserActionParams actionParams)
|
||||
{
|
||||
await _instance.Wait(actionParams.ConversationId);
|
||||
|
||||
var page = _instance.GetPage(actionParams.ConversationId);
|
||||
|
||||
if(actionParams.Context.Direction == "down")
|
||||
await page.EvaluateAsync("window.scrollBy(0, window.innerHeight - 200)");
|
||||
else if (actionParams.Context.Direction == "up")
|
||||
await page.EvaluateAsync("window.scrollBy(0, -window.innerHeight + 200)");
|
||||
else if (actionParams.Context.Direction == "left")
|
||||
await page.EvaluateAsync("window.scrollBy(-400, 0)");
|
||||
else if (actionParams.Context.Direction == "right")
|
||||
await page.EvaluateAsync("window.scrollBy(400, 0)");
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
@ -24,12 +24,12 @@ public partial class PlaywrightWebDriver : IWebBrowser
|
|||
_agent = agent;
|
||||
}
|
||||
|
||||
private ILocator? Locator(HtmlElementContextOut context)
|
||||
private ILocator? Locator(string conversationId, HtmlElementContextOut context)
|
||||
{
|
||||
ILocator element = default;
|
||||
if (!string.IsNullOrEmpty(context.ElementId))
|
||||
{
|
||||
element = _instance.Page.Locator($"#{context.ElementId}");
|
||||
element = _instance.GetPage(conversationId).Locator($"#{context.ElementId}");
|
||||
}
|
||||
else if (!string.IsNullOrEmpty(context.ElementName))
|
||||
{
|
||||
|
|
@ -40,7 +40,7 @@ public partial class PlaywrightWebDriver : IWebBrowser
|
|||
"button" => AriaRole.Button,
|
||||
_ => AriaRole.Generic
|
||||
};
|
||||
element = _instance.Page.Locator($"[name='{context.ElementName}']");
|
||||
element = _instance.GetPage(conversationId).Locator($"[name='{context.ElementName}']");
|
||||
var count = element.CountAsync().Result;
|
||||
if (count == 0)
|
||||
{
|
||||
|
|
@ -60,7 +60,7 @@ public partial class PlaywrightWebDriver : IWebBrowser
|
|||
_logger.LogError($"Can't locate the web element {context.Index}.");
|
||||
return null;
|
||||
}
|
||||
element = _instance.Page.Locator(context.TagName).Nth(context.Index);
|
||||
element = _instance.GetPage(conversationId).Locator(context.TagName).Nth(context.Index);
|
||||
}
|
||||
|
||||
return element;
|
||||
|
|
|
|||
|
|
@ -16,11 +16,12 @@ public class ChangeCheckboxFn : IFunctionCallback
|
|||
|
||||
public async Task<bool> Execute(RoleDialogModel message)
|
||||
{
|
||||
var convService = _services.GetRequiredService<IConversationService>();
|
||||
var args = JsonSerializer.Deserialize<BrowsingContextIn>(message.FunctionArgs);
|
||||
|
||||
var agentService = _services.GetRequiredService<IAgentService>();
|
||||
var agent = await agentService.LoadAgent(message.CurrentAgentId);
|
||||
var result = await _browser.ChangeCheckbox(new BrowserActionParams(agent, args, message.MessageId));
|
||||
var result = await _browser.ChangeCheckbox(new BrowserActionParams(agent, args, convService.ConversationId, message.MessageId));
|
||||
|
||||
var content = $"{(args.UpdateValue == "check" ? "Check" : "Uncheck")} checkbox of '{args.ElementText}'";
|
||||
message.Content = result ?
|
||||
|
|
@ -30,7 +31,7 @@ public class ChangeCheckboxFn : IFunctionCallback
|
|||
var webDriverService = _services.GetRequiredService<WebDriverService>();
|
||||
var path = webDriverService.GetScreenshotFilePath(message.MessageId);
|
||||
|
||||
message.Data = await _browser.ScreenshotAsync(path);
|
||||
message.Data = await _browser.ScreenshotAsync(convService.ConversationId, path);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,11 +16,12 @@ public class ChangeListValueFn : IFunctionCallback
|
|||
|
||||
public async Task<bool> Execute(RoleDialogModel message)
|
||||
{
|
||||
var convService = _services.GetRequiredService<IConversationService>();
|
||||
var args = JsonSerializer.Deserialize<BrowsingContextIn>(message.FunctionArgs);
|
||||
|
||||
var agentService = _services.GetRequiredService<IAgentService>();
|
||||
var agent = await agentService.LoadAgent(message.CurrentAgentId);
|
||||
var result = await _browser.ChangeListValue(new BrowserActionParams(agent, args, message.MessageId));
|
||||
var result = await _browser.ChangeListValue(new BrowserActionParams(agent, args, convService.ConversationId, message.MessageId));
|
||||
|
||||
var content = $"Change value to '{args.UpdateValue}' for {args.ElementName}";
|
||||
message.Content = result ?
|
||||
|
|
@ -30,7 +31,7 @@ public class ChangeListValueFn : IFunctionCallback
|
|||
var webDriverService = _services.GetRequiredService<WebDriverService>();
|
||||
var path = webDriverService.GetScreenshotFilePath(message.MessageId);
|
||||
|
||||
message.Data = await _browser.ScreenshotAsync(path);
|
||||
message.Data = await _browser.ScreenshotAsync(convService.ConversationId, path);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,11 +16,12 @@ public class CheckRadioButtonFn : IFunctionCallback
|
|||
|
||||
public async Task<bool> Execute(RoleDialogModel message)
|
||||
{
|
||||
var convService = _services.GetRequiredService<IConversationService>();
|
||||
var args = JsonSerializer.Deserialize<BrowsingContextIn>(message.FunctionArgs);
|
||||
|
||||
var agentService = _services.GetRequiredService<IAgentService>();
|
||||
var agent = await agentService.LoadAgent(message.CurrentAgentId);
|
||||
var result = await _browser.CheckRadioButton(new BrowserActionParams(agent, args, message.MessageId));
|
||||
var result = await _browser.CheckRadioButton(new BrowserActionParams(agent, args, convService.ConversationId, message.MessageId));
|
||||
|
||||
var content = $"Check value of '{args.UpdateValue}' for radio button '{args.ElementName}'";
|
||||
message.Content = result ?
|
||||
|
|
@ -30,7 +31,7 @@ public class CheckRadioButtonFn : IFunctionCallback
|
|||
var webDriverService = _services.GetRequiredService<WebDriverService>();
|
||||
var path = webDriverService.GetScreenshotFilePath(message.MessageId);
|
||||
|
||||
message.Data = await _browser.ScreenshotAsync(path);
|
||||
message.Data = await _browser.ScreenshotAsync(convService.ConversationId, path);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,11 +16,12 @@ public class ClickButtonFn : IFunctionCallback
|
|||
|
||||
public async Task<bool> Execute(RoleDialogModel message)
|
||||
{
|
||||
var convService = _services.GetRequiredService<IConversationService>();
|
||||
var args = JsonSerializer.Deserialize<BrowsingContextIn>(message.FunctionArgs);
|
||||
|
||||
var agentService = _services.GetRequiredService<IAgentService>();
|
||||
var agent = await agentService.LoadAgent(message.CurrentAgentId);
|
||||
var result = await _browser.ClickButton(new BrowserActionParams(agent, args, message.MessageId));
|
||||
var result = await _browser.ClickButton(new BrowserActionParams(agent, args, convService.ConversationId, message.MessageId));
|
||||
|
||||
var content = $"Click button of '{args.ElementName}'";
|
||||
message.Content = result ?
|
||||
|
|
@ -30,7 +31,7 @@ public class ClickButtonFn : IFunctionCallback
|
|||
var webDriverService = _services.GetRequiredService<WebDriverService>();
|
||||
var path = webDriverService.GetScreenshotFilePath(message.MessageId);
|
||||
|
||||
message.Data = await _browser.ScreenshotAsync(path);
|
||||
message.Data = await _browser.ScreenshotAsync(convService.ConversationId, path);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,11 +16,12 @@ public class ClickElementFn : IFunctionCallback
|
|||
|
||||
public async Task<bool> Execute(RoleDialogModel message)
|
||||
{
|
||||
var convService = _services.GetRequiredService<IConversationService>();
|
||||
var args = JsonSerializer.Deserialize<BrowsingContextIn>(message.FunctionArgs);
|
||||
|
||||
var agentService = _services.GetRequiredService<IAgentService>();
|
||||
var agent = await agentService.LoadAgent(message.CurrentAgentId);
|
||||
var result = await _browser.ClickElement(new BrowserActionParams(agent, args, message.MessageId));
|
||||
var result = await _browser.ClickElement(new BrowserActionParams(agent, args, convService.ConversationId, message.MessageId));
|
||||
|
||||
var content = $"Click element {args.MatchRule} text '{args.ElementText}'";
|
||||
message.Content = result ?
|
||||
|
|
@ -30,7 +31,7 @@ public class ClickElementFn : IFunctionCallback
|
|||
var webDriverService = _services.GetRequiredService<WebDriverService>();
|
||||
var path = webDriverService.GetScreenshotFilePath(message.MessageId);
|
||||
|
||||
message.Data = await _browser.ScreenshotAsync(path);
|
||||
message.Data = await _browser.ScreenshotAsync(convService.ConversationId, path);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,11 +16,12 @@ public class CloseBrowserFn : IFunctionCallback
|
|||
|
||||
public async Task<bool> Execute(RoleDialogModel message)
|
||||
{
|
||||
var convService = _services.GetRequiredService<IConversationService>();
|
||||
var args = JsonSerializer.Deserialize<BrowsingContextIn>(message.FunctionArgs);
|
||||
var agentService = _services.GetRequiredService<IAgentService>();
|
||||
var agent = await agentService.LoadAgent(message.CurrentAgentId);
|
||||
await _browser.CloseBrowser();
|
||||
message.Content = $"Browser is closed";
|
||||
await _browser.CloseBrowser(convService.ConversationId);
|
||||
message.Content = $"Browser is closed {convService.ConversationId}";
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,7 +16,8 @@ public class EvaluateScriptFn : IFunctionCallback
|
|||
|
||||
public async Task<bool> Execute(RoleDialogModel message)
|
||||
{
|
||||
message.Data = await _browser.EvaluateScript<object>(message.Content);
|
||||
var convService = _services.GetRequiredService<IConversationService>();
|
||||
message.Data = await _browser.EvaluateScript<object>(convService.ConversationId, message.Content);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,15 +16,16 @@ public class ExtractDataFn : IFunctionCallback
|
|||
|
||||
public async Task<bool> Execute(RoleDialogModel message)
|
||||
{
|
||||
var convService = _services.GetRequiredService<IConversationService>();
|
||||
var args = JsonSerializer.Deserialize<BrowsingContextIn>(message.FunctionArgs);
|
||||
var agentService = _services.GetRequiredService<IAgentService>();
|
||||
var agent = await agentService.LoadAgent(message.CurrentAgentId);
|
||||
message.Content = await _browser.ExtractData(new BrowserActionParams(agent, args, message.MessageId));
|
||||
message.Content = await _browser.ExtractData(new BrowserActionParams(agent, args, convService.ConversationId, message.MessageId));
|
||||
|
||||
var webDriverService = _services.GetRequiredService<WebDriverService>();
|
||||
var path = webDriverService.GetScreenshotFilePath(message.MessageId);
|
||||
|
||||
message.Data = await _browser.ScreenshotAsync(path);
|
||||
message.Data = await _browser.ScreenshotAsync(convService.ConversationId, path);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,17 +16,23 @@ public class GoToPageFn : IFunctionCallback
|
|||
|
||||
public async Task<bool> Execute(RoleDialogModel message)
|
||||
{
|
||||
var convService = _services.GetRequiredService<IConversationService>();
|
||||
var args = JsonSerializer.Deserialize<BrowsingContextIn>(message.FunctionArgs);
|
||||
var agentService = _services.GetRequiredService<IAgentService>();
|
||||
var agent = await agentService.LoadAgent(message.CurrentAgentId);
|
||||
await _browser.GoToPage(new BrowserActionParams(agent, args, message.MessageId));
|
||||
message.Content = $"Page {args.Url} is open.";
|
||||
|
||||
var webDriverService = _services.GetRequiredService<WebDriverService>();
|
||||
var url = webDriverService.ReplaceToken(args.Url);
|
||||
|
||||
url = url.Replace("https://https://", "https://");
|
||||
|
||||
var result = await _browser.GoToPage(convService.ConversationId, url);
|
||||
message.Content = result ? $"Page {url} is open." : $"Page {url} open failed.";
|
||||
|
||||
var path = webDriverService.GetScreenshotFilePath(message.MessageId);
|
||||
|
||||
message.Data = await _browser.ScreenshotAsync(path);
|
||||
message.Data = await _browser.ScreenshotAsync(convService.ConversationId, path);
|
||||
|
||||
return true;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,18 +16,21 @@ public class InputUserPasswordFn : IFunctionCallback
|
|||
|
||||
public async Task<bool> Execute(RoleDialogModel message)
|
||||
{
|
||||
var convService = _services.GetRequiredService<IConversationService>();
|
||||
var args = JsonSerializer.Deserialize<BrowsingContextIn>(message.FunctionArgs);
|
||||
|
||||
var agentService = _services.GetRequiredService<IAgentService>();
|
||||
var agent = await agentService.LoadAgent(message.CurrentAgentId);
|
||||
var result = await _browser.InputUserPassword(new BrowserActionParams(agent, args, message.MessageId));
|
||||
|
||||
var webDriverService = _services.GetRequiredService<WebDriverService>();
|
||||
args.Password = webDriverService.ReplaceToken(args.Password);
|
||||
var result = await _browser.InputUserPassword(new BrowserActionParams(agent, args, convService.ConversationId, message.MessageId));
|
||||
|
||||
message.Content = result ? "Input password successfully" : "Input password failed";
|
||||
|
||||
var webDriverService = _services.GetRequiredService<WebDriverService>();
|
||||
var path = webDriverService.GetScreenshotFilePath(message.MessageId);
|
||||
|
||||
message.Data = await _browser.ScreenshotAsync(path);
|
||||
message.Data = await _browser.ScreenshotAsync(convService.ConversationId, path);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,11 +16,12 @@ public class InputUserTextFn : IFunctionCallback
|
|||
|
||||
public async Task<bool> Execute(RoleDialogModel message)
|
||||
{
|
||||
var convService = _services.GetRequiredService<IConversationService>();
|
||||
var args = JsonSerializer.Deserialize<BrowsingContextIn>(message.FunctionArgs);
|
||||
|
||||
var agentService = _services.GetRequiredService<IAgentService>();
|
||||
var agent = await agentService.LoadAgent(message.CurrentAgentId);
|
||||
var result = await _browser.InputUserText(new BrowserActionParams(agent, args, message.MessageId));
|
||||
var result = await _browser.InputUserText(new BrowserActionParams(agent, args, convService.ConversationId, message.MessageId));
|
||||
|
||||
var content = $"Input '{args.InputText}' in element '{args.ElementText}'";
|
||||
if (args.PressEnter != null && args.PressEnter == true)
|
||||
|
|
@ -34,8 +35,8 @@ public class InputUserTextFn : IFunctionCallback
|
|||
|
||||
var webDriverService = _services.GetRequiredService<WebDriverService>();
|
||||
var path = webDriverService.GetScreenshotFilePath(message.MessageId);
|
||||
|
||||
message.Data = await _browser.ScreenshotAsync(path);
|
||||
|
||||
message.Data = await _browser.ScreenshotAsync(convService.ConversationId, path);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,15 +16,28 @@ public class OpenBrowserFn : IFunctionCallback
|
|||
|
||||
public async Task<bool> Execute(RoleDialogModel message)
|
||||
{
|
||||
var convService = _services.GetRequiredService<IConversationService>();
|
||||
var args = JsonSerializer.Deserialize<BrowsingContextIn>(message.FunctionArgs);
|
||||
await _browser.LaunchBrowser(args.Url);
|
||||
message.Content = string.IsNullOrEmpty(args.Url) ? $"Launch browser with blank page successfully." : $"Open website {args.Url} successfully.";
|
||||
|
||||
var webDriverService = _services.GetRequiredService<WebDriverService>();
|
||||
var url = webDriverService.ReplaceToken(args.Url);
|
||||
|
||||
url = url.Replace("https://https://", "https://");
|
||||
var result = await _browser.LaunchBrowser(convService.ConversationId, url);
|
||||
|
||||
if (result)
|
||||
{
|
||||
message.Content = string.IsNullOrEmpty(url) ? $"Launch browser with blank page successfully." : $"Open website {url} successfully.";
|
||||
}
|
||||
else
|
||||
{
|
||||
message.Content = "Launch browser failed.";
|
||||
}
|
||||
|
||||
var path = webDriverService.GetScreenshotFilePath(message.MessageId);
|
||||
|
||||
message.Data = await _browser.ScreenshotAsync(path);
|
||||
message.Data = await _browser.ScreenshotAsync(convService.ConversationId, path);
|
||||
|
||||
return true;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,29 @@
|
|||
namespace BotSharp.Plugin.WebDriver.Functions;
|
||||
|
||||
public class ScreenshotFn : IFunctionCallback
|
||||
{
|
||||
public string Name => "take_screenshot";
|
||||
|
||||
private readonly IServiceProvider _services;
|
||||
private readonly IWebBrowser _browser;
|
||||
|
||||
public ScreenshotFn(IServiceProvider services,
|
||||
IWebBrowser browser)
|
||||
{
|
||||
_services = services;
|
||||
_browser = browser;
|
||||
}
|
||||
|
||||
public async Task<bool> Execute(RoleDialogModel message)
|
||||
{
|
||||
var convService = _services.GetRequiredService<IConversationService>();
|
||||
|
||||
var webDriverService = _services.GetRequiredService<WebDriverService>();
|
||||
var path = webDriverService.GetScreenshotFilePath(message.MessageId);
|
||||
|
||||
message.Data = await _browser.ScreenshotAsync(convService.ConversationId, path);
|
||||
message.Content = "Took screenshot completed. You can take another screenshot if needed.";
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
namespace BotSharp.Plugin.WebDriver.Functions;
|
||||
|
||||
public class ScrollPageFn : IFunctionCallback
|
||||
{
|
||||
public string Name => "scroll_page";
|
||||
|
||||
private readonly IServiceProvider _services;
|
||||
private readonly IWebBrowser _browser;
|
||||
|
||||
public ScrollPageFn(IServiceProvider services,
|
||||
IWebBrowser browser)
|
||||
{
|
||||
_services = services;
|
||||
_browser = browser;
|
||||
}
|
||||
|
||||
public async Task<bool> Execute(RoleDialogModel message)
|
||||
{
|
||||
var convService = _services.GetRequiredService<IConversationService>();
|
||||
var args = JsonSerializer.Deserialize<BrowsingContextIn>(message.FunctionArgs);
|
||||
|
||||
var agentService = _services.GetRequiredService<IAgentService>();
|
||||
var agent = await agentService.LoadAgent(message.CurrentAgentId);
|
||||
|
||||
message.Data = await _browser.ScrollPageAsync(new BrowserActionParams(agent, args, convService.ConversationId, message.MessageId));
|
||||
message.Content = "Scrolled. You can scroll more if needed.";
|
||||
|
||||
var webDriverService = _services.GetRequiredService<WebDriverService>();
|
||||
var path = webDriverService.GetScreenshotFilePath(message.MessageId);
|
||||
|
||||
message.Data = await _browser.ScreenshotAsync(convService.ConversationId, path);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,3 +1,5 @@
|
|||
using BotSharp.Abstraction.Agents.Enums;
|
||||
|
||||
namespace BotSharp.Plugin.WebDriver.Hooks;
|
||||
|
||||
public class WebDriverConversationHook : ConversationHookBase
|
||||
|
|
@ -13,7 +15,11 @@ public class WebDriverConversationHook : ConversationHookBase
|
|||
var webDriverService = _services.GetRequiredService<WebDriverService>();
|
||||
|
||||
// load screenshot
|
||||
dialog.Data = "data:image/png;base64," + webDriverService.GetScreenshotBase64(dialog.MessageId);
|
||||
if (dialog.Role == AgentRole.Assistant)
|
||||
{
|
||||
dialog.Data = "data:image/png;base64," + webDriverService.GetScreenshotBase64(dialog.MessageId);
|
||||
}
|
||||
|
||||
await base.OnDialogRecordLoaded(dialog);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,6 +19,12 @@ public class BrowsingContextIn
|
|||
[JsonPropertyName("element_text")]
|
||||
public string? ElementText { get; set; }
|
||||
|
||||
[JsonPropertyName("attribute_name")]
|
||||
public string? AttributeName { get; set; }
|
||||
|
||||
[JsonPropertyName("attribute_value")]
|
||||
public string? AttributeValue { get; set; }
|
||||
|
||||
[JsonPropertyName("press_enter")]
|
||||
public bool? PressEnter { get; set; }
|
||||
|
||||
|
|
@ -33,4 +39,7 @@ public class BrowsingContextIn
|
|||
|
||||
[JsonPropertyName("question")]
|
||||
public string? Question { get; set; }
|
||||
|
||||
[JsonPropertyName("direction")]
|
||||
public string? Direction { get; set; }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,12 +4,14 @@ public class BrowserActionParams
|
|||
{
|
||||
public Agent Agent { get; set; }
|
||||
public BrowsingContextIn Context { get; set; }
|
||||
public string ConversationId { get; set; }
|
||||
public string MessageId { get; set; }
|
||||
|
||||
public BrowserActionParams(Agent agent, BrowsingContextIn context, string messageId)
|
||||
public BrowserActionParams(Agent agent, BrowsingContextIn context, string conversationId, string messageId)
|
||||
{
|
||||
Agent = agent;
|
||||
Context = context;
|
||||
ConversationId = conversationId;
|
||||
MessageId = messageId;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
using BotSharp.Plugin.WebDriver.LlmContexts;
|
||||
|
||||
namespace BotSharp.Plugin.WebDriver.Services;
|
||||
|
||||
public partial class WebDriverService
|
||||
|
|
|
|||
|
|
@ -0,0 +1,24 @@
|
|||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace BotSharp.Plugin.WebDriver.Services;
|
||||
|
||||
public partial class WebDriverService
|
||||
{
|
||||
/// <summary>
|
||||
/// Replace token started @ with settings.
|
||||
/// </summary>
|
||||
/// <param name="text"></param>
|
||||
/// <returns></returns>
|
||||
public string ReplaceToken(string text)
|
||||
{
|
||||
var config = _services.GetRequiredService<IConfiguration>();
|
||||
var token = Regex.Match(text, "@[a-zA-Z0-9._]+");
|
||||
if (token.Success)
|
||||
{
|
||||
var key = token.Value.Replace("@", "").Replace(".", ":");
|
||||
var value = config.GetValue<string>(key);
|
||||
return text.Replace(token.Value, value);
|
||||
}
|
||||
return text;
|
||||
}
|
||||
}
|
||||
|
|
@ -37,6 +37,30 @@
|
|||
"required": [ "url" ]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "scroll_page",
|
||||
"description": "Scroll page down or up",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"direction": {
|
||||
"type": "string",
|
||||
"description": "down, up, left, right"
|
||||
}
|
||||
},
|
||||
"required": [ "direction" ]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "take_screenshot",
|
||||
"description": "Tak screenshot to show current page screen",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
},
|
||||
"required": []
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "click_button",
|
||||
"description": "Click a button in a web page.",
|
||||
|
|
@ -82,6 +106,14 @@
|
|||
"press_enter": {
|
||||
"type": "boolean",
|
||||
"description": "whether to press Enter key"
|
||||
},
|
||||
"attribute_name": {
|
||||
"type": "string",
|
||||
"description": "attribute name in the element"
|
||||
},
|
||||
"attribute_value": {
|
||||
"type": "string",
|
||||
"description": "attribute value in the element"
|
||||
}
|
||||
},
|
||||
"required": [ "element_text", "input_text" ]
|
||||
|
|
@ -155,6 +187,14 @@
|
|||
"type": "string",
|
||||
"description": "text or placeholder shown in the element."
|
||||
},
|
||||
"attribute_name": {
|
||||
"type": "string",
|
||||
"description": "attribute name in the element"
|
||||
},
|
||||
"attribute_value": {
|
||||
"type": "string",
|
||||
"description": "attribute value in the element"
|
||||
},
|
||||
"match_rule": {
|
||||
"type": "string",
|
||||
"description": "text matching rule: EndWith, StartWith, Contains, Match"
|
||||
|
|
|
|||
|
|
@ -13,6 +13,24 @@
|
|||
"Key": "31ba6052aa6f4569901facc3a41fcb4adfd9b46dd00c40af8a753fbdc2b89869"
|
||||
},
|
||||
|
||||
"OAuth": {
|
||||
"GitHub": {
|
||||
"ClientId": "",
|
||||
"ClientSecret": ""
|
||||
},
|
||||
"Google": {
|
||||
"ClientId": "",
|
||||
"ClientSecret": ""
|
||||
},
|
||||
"Keycloak": {
|
||||
"BaseAddress": "",
|
||||
"Realm": "",
|
||||
"ClientId": "",
|
||||
"ClientSecret": "",
|
||||
"Version": 22
|
||||
}
|
||||
},
|
||||
|
||||
"LlmProviders": [
|
||||
{
|
||||
"Provider": "azure-openai",
|
||||
|
|
@ -80,7 +98,9 @@
|
|||
"DataDir": "conversations",
|
||||
"ShowVerboseLog": false,
|
||||
"EnableLlmCompletionLog": false,
|
||||
"EnableExecutionLog": true
|
||||
"EnableExecutionLog": true,
|
||||
"EnableContentLog": true,
|
||||
"EnableStateLog": true
|
||||
},
|
||||
|
||||
"Statistics": {
|
||||
|
|
|
|||
Loading…
Reference in a new issue