Merge branch 'SciSharp:master' into master

This commit is contained in:
hchen2020 2025-02-21 11:43:03 -06:00 committed by GitHub
commit 2403d46826
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
34 changed files with 162 additions and 182 deletions

View file

@ -5,7 +5,7 @@
[![QQ群聊](https://img.shields.io/static/v1?label=QQ&message=群聊&color=brightgreen)](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=sN9VVMwbWjs5L0ATpizKKxOcZdEPMrp8&authKey=RLDw41bLTrEyEgZZi%2FzT4pYk%2BwmEFgFcrhs8ZbkiVY7a4JFckzJefaYNW6Lk4yPX&noverify=0&group_code=985366726)
[![Apache 2.0](https://img.shields.io/hexpm/l/plug.svg)](https://raw.githubusercontent.com/Oceania2018/BotSharp/master/LICENSE)
[![NuGet](https://img.shields.io/nuget/dt/BotSharp.Core.svg)](https://www.nuget.org/packages/BotSharp.Core)
[![Build status](https://ci.appveyor.com/api/projects/status/qx2dx5ca5hjqodm5?svg=true)](https://ci.appveyor.com/project/Haiping-Chen/botsharp)
[![build](https://github.com/SciSharp/BotSharp/actions/workflows/build.yml/badge.svg)](https://github.com/SciSharp/BotSharp/actions/workflows/build.yml)
[![Documentation Status](https://readthedocs.org/projects/botsharp/badge/?version=latest)](https://botsharp.readthedocs.io/en/latest/?badge=latest)
*"Conversation as a platform (CaaP) is the future, so it's perfect that we're already offering the whole toolkits to our .NET developers using the BotSharp AI BOT Platform Builder to build a CaaP. It opens up as much learning power as possible for your own robots and precisely control every step of the AI processing pipeline."*

View file

@ -25,6 +25,7 @@ public interface IWebBrowser
Task<string> ExtractData(BrowserActionParams actionParams);
Task<T> EvaluateScript<T>(string contextId, string script);
Task CloseBrowser(string contextId);
Task<bool> IsBrowserClosed(string contextId);
Task<BrowserActionResult> CloseCurrentPage(MessageInfo message);
Task<BrowserActionResult> SendHttpRequest(MessageInfo message, HttpRequestParams actionParams);
Task<BrowserActionResult> GetAttributeValue(MessageInfo message, ElementLocatingArgs location);

View file

@ -6,7 +6,7 @@ namespace BotSharp.Abstraction.Conversations;
/// <summary>
/// Conversation state service to track the context in the conversation lifecycle
/// </summary>
public interface IConversationStateService
public interface IConversationStateService : IDisposable
{
string GetConversationId();
Dictionary<string, string> Load(string conversationId, bool isReadOnly = false);

View file

@ -7,5 +7,5 @@ public class LlmCompletionLog
public string AgentId { get; set; } = string.Empty;
public string Prompt { get; set; } = string.Empty;
public string? Response { get; set; }
public DateTime CreateDateTime { get; set; } = DateTime.UtcNow;
public DateTime CreatedTime { get; set; } = DateTime.UtcNow;
}

View file

@ -8,4 +8,5 @@ public interface ILlmProviderService
List<string> GetProviders();
LlmModelSetting GetProviderModel(string provider, string id, bool? multiModal = null, bool? realTime = false, bool imageGenerate = false);
List<LlmModelSetting> GetProviderModels(string provider);
List<LlmProviderSetting> GetLlmConfigs(LlmConfigOptions? options = null);
}

View file

@ -0,0 +1,9 @@
namespace BotSharp.Abstraction.MLTasks.Settings;
public class LlmConfigOptions
{
public LlmModelType? Type { get; set; }
public bool? MultiModal { get; set; }
public bool? RealTime { get; set; }
public bool? ImageGeneration { get; set; }
}

View file

@ -2,11 +2,9 @@ namespace BotSharp.Abstraction.MLTasks.Settings;
public class LlmProviderSetting
{
public string Provider { get; set; }
= "azure-openai";
public string Provider { get; set; } = "azure-openai";
public List<LlmModelSetting> Models { get; set; }
= new List<LlmModelSetting>();
public List<LlmModelSetting> Models { get; set; } = [];
public override string ToString()
{

View file

@ -152,13 +152,6 @@ public interface IBotSharpRepository : IHaveServiceProvider
=> throw new NotImplementedException();
#endregion
#region Execution Log
void AddExecutionLogs(string conversationId, List<string> logs)
=> throw new NotImplementedException();
List<string> GetExecutionLogs(string conversationId)
=> throw new NotImplementedException();
#endregion
#region LLM Completion Log
void SaveLlmCompletionLog(LlmCompletionLog log)
=> throw new NotImplementedException();

View file

@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>$(TargetFramework)</TargetFramework>

View file

@ -22,12 +22,12 @@ namespace BotSharp.Core.Conversations.Services;
/// <summary>
/// Maintain the conversation state
/// </summary>
public class ConversationStateService : IConversationStateService, IDisposable
public class ConversationStateService : IConversationStateService
{
private readonly ILogger _logger;
private readonly IServiceProvider _services;
private readonly IBotSharpRepository _db;
private readonly IConversationSideCar _sidecar;
private readonly IConversationSideCar? _sidecar;
private string _conversationId;
/// <summary>
/// States in the current round of conversation
@ -41,15 +41,14 @@ public class ConversationStateService : IConversationStateService, IDisposable
public ConversationStateService(
IServiceProvider services,
IBotSharpRepository db,
IConversationSideCar sidecar,
ILogger<ConversationStateService> logger)
{
_services = services;
_db = db;
_sidecar = sidecar;
_logger = logger;
_curStates = new ConversationState();
_historyStates = new ConversationState();
_sidecar = services.GetService<IConversationSideCar>();
}
public string GetConversationId() => _conversationId;

View file

@ -1,26 +1,25 @@
using BotSharp.Abstraction.Evaluations;
using BotSharp.Abstraction.Repositories;
using System.Text.RegularExpressions;
namespace BotSharp.Core.Evaluations;
public class ExecutionLogger : IExecutionLogger
{
private readonly BotSharpDatabaseSettings _dbSettings;
private readonly IServiceProvider _services;
private readonly ILogger<ExecutionLogger> _logger;
public ExecutionLogger(
BotSharpDatabaseSettings dbSettings,
IServiceProvider services)
IServiceProvider services,
ILogger<ExecutionLogger> logger)
{
_dbSettings = dbSettings;
_services = services;
_logger = logger;
}
public void Append(string conversationId, string content)
{
content = content.Replace("\r\n", " ").Replace("\n", " ");
content = Regex.Replace(content, @"\s+", " ");
var db = _services.GetRequiredService<IBotSharpRepository>();
db.AddExecutionLogs(conversationId, new List<string> { content });
_logger.LogInformation($"Execution Log: {content}");
}
}

View file

@ -103,4 +103,50 @@ public class LlmProviderService : ILlmProviderService
return modelSetting;
}
public List<LlmProviderSetting> GetLlmConfigs(LlmConfigOptions? options = null)
{
var settingService = _services.GetRequiredService<ISettingService>();
var providers = settingService.Bind<List<LlmProviderSetting>>($"LlmProviders");
var configs = new List<LlmProviderSetting>();
if (providers.IsNullOrEmpty()) return configs;
if (options == null) return providers ?? [];
foreach (var provider in providers)
{
var models = provider.Models ?? [];
if (options.Type.HasValue)
{
models = models.Where(x => x.Type == options.Type.Value).ToList();
}
if (options.MultiModal.HasValue)
{
models = models.Where(x => x.MultiModal == options.MultiModal.Value).ToList();
}
if (options.ImageGeneration.HasValue)
{
models = models.Where(x => x.ImageGeneration == options.ImageGeneration.Value).ToList();
}
if (options.RealTime.HasValue)
{
models = models.Where(x => x.RealTime == options.RealTime.Value).ToList();
}
if (models.IsNullOrEmpty())
{
continue;
}
provider.Models = models;
configs.Add(provider);
}
return configs;
}
}

View file

@ -0,0 +1,23 @@
using BotSharp.Abstraction.Plugins.Models;
using Microsoft.Extensions.Configuration;
namespace BotSharp.Core.Instructs;
public class InsturctionPlugin : IBotSharpPlugin
{
public string Id => "8189e133-819c-4505-9f82-84f793bc1be0";
public string Name => "Instruction";
public string Description => "Handle agent instruction request";
public void RegisterDI(IServiceCollection services, IConfiguration config)
{
}
public bool AttachMenu(List<PluginMenuDef> menu)
{
var section = menu.First(x => x.Label == "Apps");
menu.Add(new PluginMenuDef("Instruction", link: "page/instruction", icon: "bx bx-book-content", weight: section.Weight + 5));
return true;
}
}

View file

@ -135,18 +135,6 @@ public class BotSharpDbContext : Database, IBotSharpRepository
=> throw new NotImplementedException();
#endregion
#region Execution Log
public void AddExecutionLogs(string conversationId, List<string> logs)
{
throw new NotImplementedException();
}
public List<string> GetExecutionLogs(string conversationId)
{
throw new NotImplementedException();
}
#endregion
#region LLM Completion Log
public void SaveLlmCompletionLog(LlmCompletionLog log)
{

View file

@ -1,40 +1,10 @@
using BotSharp.Abstraction.Loggers.Models;
using Serilog;
using System.IO;
namespace BotSharp.Core.Repository
{
public partial class FileRepository
{
#region Execution Log
public void AddExecutionLogs(string conversationId, List<string> logs)
{
if (string.IsNullOrEmpty(conversationId) || logs.IsNullOrEmpty()) return;
var dir = Path.Combine(_dbSettings.FileRepository, "conversations", conversationId);
if (!Directory.Exists(dir))
{
Directory.CreateDirectory(dir);
}
var file = Path.Combine(dir, EXECUTION_LOG_FILE);
File.AppendAllLines(file, logs);
}
public List<string> GetExecutionLogs(string conversationId)
{
var logs = new List<string>();
if (string.IsNullOrEmpty(conversationId)) return logs;
var dir = Path.Combine(_dbSettings.FileRepository, "conversations", conversationId);
if (!Directory.Exists(dir)) return logs;
var file = Path.Combine(dir, EXECUTION_LOG_FILE);
logs = File.ReadAllLines(file)?.ToList() ?? new List<string>();
return logs;
}
#endregion
#region LLM Completion Log
public void SaveLlmCompletionLog(LlmCompletionLog log)
{

View file

@ -50,9 +50,6 @@ public partial class FileRepository : IBotSharpRepository
private const string KNOWLEDGE_DOC_FOLDER = "document";
private const string KNOWLEDGE_DOC_META_FILE = "meta.json";
private const string EXECUTION_LOG_FILE = "execution.log";
private const string PLUGIN_CONFIG_FILE = "config.json";
private const string STATS_FOLDER = "stats";
private const string STATS_FILE = "stats.json";

View file

@ -29,7 +29,8 @@ public class CommonContentGeneratingHook : IContentGeneratingHook
MessageId = message.MessageId,
AgentId = message.CurrentAgentId,
Prompt = tokenStats.Prompt,
Response = message.Content
Response = message.Content,
CreatedTime = DateTime.UtcNow
};
db.SaveLlmCompletionLog(completionLog);

View file

@ -79,8 +79,9 @@ public class ConversationController : ControllerBase
[HttpGet("/conversation/{conversationId}/dialogs")]
public async Task<IEnumerable<ChatResponseModel>> GetDialogs([FromRoute] string conversationId)
{
var storage = _services.GetRequiredService<IConversationStorage>();
var history = storage.GetDialogs(conversationId);
var conv = _services.GetRequiredService<IConversationService>();
conv.SetConversationId(conversationId, [], isReadOnly: true);
var history = conv.GetDialogHistory(fromBreakpoint: false);
var userService = _services.GetRequiredService<IUserService>();
var agentService = _services.GetRequiredService<IAgentService>();

View file

@ -44,7 +44,7 @@ public class InstructModeController : ControllerBase
}
[HttpPost("/instruct/text-completion")]
public async Task<string> TextCompletion([FromBody] IncomingMessageModel input)
public async Task<string> TextCompletion([FromBody] IncomingInstructRequest input)
{
var state = _services.GetRequiredService<IConversationStateService>();
input.States.ForEach(x => state.SetState(x.Key, x.Value, activeRounds: x.ActiveRounds, source: StateSource.External));
@ -53,12 +53,12 @@ public class InstructModeController : ControllerBase
.SetState("model_id", input.ModelId, source: StateSource.External);
var textCompletion = CompletionProvider.GetTextCompletion(_services);
return await textCompletion.GetCompletion(input.Text, Guid.Empty.ToString(), Guid.NewGuid().ToString());
return await textCompletion.GetCompletion(input.Text, input.AgentId ?? Guid.Empty.ToString(), Guid.NewGuid().ToString());
}
#region Chat
[HttpPost("/instruct/chat-completion")]
public async Task<string> ChatCompletion([FromBody] IncomingMessageModel input)
public async Task<string> ChatCompletion([FromBody] IncomingInstructRequest input)
{
var state = _services.GetRequiredService<IConversationStateService>();
input.States.ForEach(x => state.SetState(x.Key, x.Value, activeRounds: x.ActiveRounds, source: StateSource.External));
@ -66,10 +66,11 @@ public class InstructModeController : ControllerBase
.SetState("model", input.Model, source: StateSource.External)
.SetState("model_id", input.ModelId, source: StateSource.External);
var textCompletion = CompletionProvider.GetChatCompletion(_services);
var message = await textCompletion.GetChatCompletions(new Agent()
var completion = CompletionProvider.GetChatCompletion(_services);
var message = await completion.GetChatCompletions(new Agent()
{
Id = Guid.Empty.ToString(),
Id = input.AgentId ?? Guid.Empty.ToString(),
Instruction = input.Instruction
}, new List<RoleDialogModel>
{
new RoleDialogModel(AgentRole.User, input.Text)

View file

@ -27,4 +27,11 @@ public class LlmProviderController : ControllerBase
var list = _llmProvider.GetProviderModels(provider);
return list.Where(x => x.Type == LlmModelType.Chat);
}
[HttpGet("/llm-configs")]
public List<LlmProviderSetting> GetLlmConfigs([FromQuery] LlmConfigOptions options)
{
var configs = _llmProvider.GetLlmConfigs(options);
return configs;
}
}

View file

@ -9,3 +9,10 @@ public class InstructMessageModel : IncomingMessageModel
public override string Channel { get; set; } = ConversationChannel.OpenAPI;
public string? Template { get; set; }
}
public class IncomingInstructRequest : IncomingMessageModel
{
public string? AgentId { get; set; }
public string? Instruction { get; set; }
}

View file

@ -1,7 +0,0 @@
namespace BotSharp.Plugin.MongoStorage.Collections;
public class ExecutionLogDocument : MongoBase
{
public string ConversationId { get; set; } = default!;
public List<string> Logs { get; set; } = [];
}

View file

@ -3,5 +3,9 @@ namespace BotSharp.Plugin.MongoStorage.Collections;
public class LlmCompletionLogDocument : MongoBase
{
public string ConversationId { get; set; } = default!;
public List<PromptLogMongoElement> Logs { get; set; } = [];
public string MessageId { get; set; } = default!;
public string AgentId { get; set; } = default!;
public string Prompt { get; set; } = default!;
public string? Response { get; set; }
public DateTime CreatedTime { get; set; }
}

View file

@ -1,11 +0,0 @@
namespace BotSharp.Plugin.MongoStorage.Models;
[BsonIgnoreExtraElements(Inherited = true)]
public class PromptLogMongoElement
{
public string MessageId { get; set; } = default!;
public string AgentId { get; set; } = default!;
public string Prompt { get; set; } = default!;
public string? Response { get; set; }
public DateTime CreateDateTime { get; set; }
}

View file

@ -154,9 +154,6 @@ public class MongoDbContext
public IMongoCollection<ConversationStateDocument> ConversationStates
=> CreateConversationStateIndex();
public IMongoCollection<ExecutionLogDocument> ExectionLogs
=> GetCollectionOrCreate<ExecutionLogDocument>("ExecutionLogs");
public IMongoCollection<LlmCompletionLogDocument> LlmCompletionLogs
=> GetCollectionOrCreate<LlmCompletionLogDocument>("LlmCompletionLogs");

View file

@ -56,13 +56,11 @@ public partial class MongoRepository
var filterConv = Builders<ConversationDocument>.Filter.In(x => x.Id, conversationIds);
var filterDialog = Builders<ConversationDialogDocument>.Filter.In(x => x.ConversationId, conversationIds);
var filterSates = Builders<ConversationStateDocument>.Filter.In(x => x.ConversationId, conversationIds);
var filterExeLog = Builders<ExecutionLogDocument>.Filter.In(x => x.ConversationId, conversationIds);
var filterPromptLog = Builders<LlmCompletionLogDocument>.Filter.In(x => x.ConversationId, conversationIds);
var filterContentLog = Builders<ConversationContentLogDocument>.Filter.In(x => x.ConversationId, conversationIds);
var filterStateLog = Builders<ConversationStateLogDocument>.Filter.In(x => x.ConversationId, conversationIds);
var conbTabItems = Builders<CrontabItemDocument>.Filter.In(x => x.ConversationId, conversationIds);
var exeLogDeleted = _dc.ExectionLogs.DeleteMany(filterExeLog);
var promptLogDeleted = _dc.LlmCompletionLogs.DeleteMany(filterPromptLog);
var contentLogDeleted = _dc.ContentLogs.DeleteMany(filterContentLog);
var stateLogDeleted = _dc.StateLogs.DeleteMany(filterStateLog);
@ -71,10 +69,8 @@ public partial class MongoRepository
var cronDeleted = _dc.CrontabItems.DeleteMany(conbTabItems);
var convDeleted = _dc.Conversations.DeleteMany(filterConv);
return convDeleted.DeletedCount > 0 || dialogDeleted.DeletedCount > 0 || statesDeleted.DeletedCount > 0
|| exeLogDeleted.DeletedCount > 0 || promptLogDeleted.DeletedCount > 0
|| contentLogDeleted.DeletedCount > 0 || stateLogDeleted.DeletedCount > 0
|| convDeleted.DeletedCount > 0;
return convDeleted.DeletedCount > 0 || dialogDeleted.DeletedCount > 0 || statesDeleted.DeletedCount > 0 || promptLogDeleted.DeletedCount > 0
|| contentLogDeleted.DeletedCount > 0 || stateLogDeleted.DeletedCount > 0 || convDeleted.DeletedCount > 0;
}
[SideCar]

View file

@ -4,32 +4,6 @@ namespace BotSharp.Plugin.MongoStorage.Repository;
public partial class MongoRepository
{
#region Execution Log
public void AddExecutionLogs(string conversationId, List<string> logs)
{
if (string.IsNullOrEmpty(conversationId) || logs.IsNullOrEmpty()) return;
var filter = Builders<ExecutionLogDocument>.Filter.Eq(x => x.ConversationId, conversationId);
var update = Builders<ExecutionLogDocument>.Update
.SetOnInsert(x => x.Id, Guid.NewGuid().ToString())
.PushEach(x => x.Logs, logs);
_dc.ExectionLogs.UpdateOne(filter, update, _options);
}
public List<string> GetExecutionLogs(string conversationId)
{
List<string> logs = [];
if (string.IsNullOrEmpty(conversationId)) return logs;
var filter = Builders<ExecutionLogDocument>.Filter.Eq(x => x.ConversationId, conversationId);
var logCollection = _dc.ExectionLogs.Find(filter).FirstOrDefault();
logs = logCollection?.Logs ?? [];
return logs;
}
#endregion
#region LLM Completion Log
public void SaveLlmCompletionLog(LlmCompletionLog log)
{
@ -38,21 +12,17 @@ public partial class MongoRepository
var conversationId = log.ConversationId.IfNullOrEmptyAs(Guid.NewGuid().ToString());
var messageId = log.MessageId.IfNullOrEmptyAs(Guid.NewGuid().ToString());
var logElement = new PromptLogMongoElement
var data = new LlmCompletionLogDocument
{
Id = Guid.NewGuid().ToString(),
ConversationId = conversationId,
MessageId = messageId,
AgentId = log.AgentId,
Prompt = log.Prompt,
Response = log.Response,
CreateDateTime = log.CreateDateTime
CreatedTime = log.CreatedTime
};
var filter = Builders<LlmCompletionLogDocument>.Filter.Eq(x => x.ConversationId, conversationId);
var update = Builders<LlmCompletionLogDocument>.Update
.SetOnInsert(x => x.Id, Guid.NewGuid().ToString())
.Push(x => x.Logs, logElement);
_dc.LlmCompletionLogs.UpdateOne(filter, update, _options);
_dc.LlmCompletionLogs.InsertOne(data);
}
#endregion

View file

@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>$(TargetFramework)</TargetFramework>
@ -16,21 +16,8 @@
</ItemGroup>
<ItemGroup>
<None Remove="data\agents\f3ae2a0f-e6ba-4ee1-a0b9-75d7431ff32b\agent.json" />
<None Remove="data\agents\f3ae2a0f-e6ba-4ee1-a0b9-75d7431ff32b\functions\change_checkbox.json" />
<None Remove="data\agents\f3ae2a0f-e6ba-4ee1-a0b9-75d7431ff32b\functions\change_list_value.json" />
<None Remove="data\agents\f3ae2a0f-e6ba-4ee1-a0b9-75d7431ff32b\functions\check_radio_button.json" />
<None Remove="data\agents\f3ae2a0f-e6ba-4ee1-a0b9-75d7431ff32b\functions\click_button.json" />
<None Remove="data\agents\f3ae2a0f-e6ba-4ee1-a0b9-75d7431ff32b\functions\click_element.json" />
<None Remove="data\agents\f3ae2a0f-e6ba-4ee1-a0b9-75d7431ff32b\functions\close_browser.json" />
<None Remove="data\agents\f3ae2a0f-e6ba-4ee1-a0b9-75d7431ff32b\functions\extract_data_from_page.json" />
<None Remove="data\agents\f3ae2a0f-e6ba-4ee1-a0b9-75d7431ff32b\functions\go_to_page.json" />
<None Remove="data\agents\f3ae2a0f-e6ba-4ee1-a0b9-75d7431ff32b\functions\input_user_password.json" />
<None Remove="data\agents\f3ae2a0f-e6ba-4ee1-a0b9-75d7431ff32b\functions\input_user_text.json" />
<None Remove="data\agents\f3ae2a0f-e6ba-4ee1-a0b9-75d7431ff32b\functions\open_browser.json" />
<None Remove="data\agents\f3ae2a0f-e6ba-4ee1-a0b9-75d7431ff32b\functions\scroll_page.json" />
<None Remove="data\agents\f3ae2a0f-e6ba-4ee1-a0b9-75d7431ff32b\functions\send_http_request.json" />
<None Remove="data\agents\f3ae2a0f-e6ba-4ee1-a0b9-75d7431ff32b\functions\take_screenshot.json" />
<None Remove="data\agents\6745151e-6d46-4a02-8de4-1c4f21c7da95\templates\util-web-action_on_element.fn.liquid" />
<None Remove="data\agents\6745151e-6d46-4a02-8de4-1c4f21c7da95\templates\util-web-go_to_page.fn.liquid" />
<None Remove="data\agents\f3ae2a0f-e6ba-4ee1-a0b9-75d7431ff32b\instructions\instruction.liquid" />
<None Remove="data\agents\f3ae2a0f-e6ba-4ee1-a0b9-75d7431ff32b\templates\extract_data.liquid" />
<None Remove="data\agents\f3ae2a0f-e6ba-4ee1-a0b9-75d7431ff32b\templates\html_parser.liquid" />
@ -38,9 +25,6 @@
</ItemGroup>
<ItemGroup>
<Content Include="data\agents\6745151e-6d46-4a02-8de4-1c4f21c7da95\functions\util-web-locate_element.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="data\agents\6745151e-6d46-4a02-8de4-1c4f21c7da95\functions\util-web-action_on_element.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
@ -50,6 +34,9 @@
<Content Include="data\agents\6745151e-6d46-4a02-8de4-1c4f21c7da95\functions\util-web-go_to_page.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="data\agents\6745151e-6d46-4a02-8de4-1c4f21c7da95\functions\util-web-locate_element.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="data\agents\6745151e-6d46-4a02-8de4-1c4f21c7da95\templates\util-web-action_on_element.fn.liquid">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
@ -59,15 +46,6 @@
<Content Include="data\agents\f3ae2a0f-e6ba-4ee1-a0b9-75d7431ff32b\agent.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="data\agents\f3ae2a0f-e6ba-4ee1-a0b9-75d7431ff32b\instructions\instruction.liquid">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="data\agents\f3ae2a0f-e6ba-4ee1-a0b9-75d7431ff32b\templates\extract_data.liquid">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="data\agents\f3ae2a0f-e6ba-4ee1-a0b9-75d7431ff32b\templates\html_parser.liquid">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="data\agents\f3ae2a0f-e6ba-4ee1-a0b9-75d7431ff32b\functions\change_checkbox.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
@ -110,6 +88,15 @@
<Content Include="data\agents\f3ae2a0f-e6ba-4ee1-a0b9-75d7431ff32b\functions\take_screenshot.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="data\agents\f3ae2a0f-e6ba-4ee1-a0b9-75d7431ff32b\instructions\instruction.liquid">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="data\agents\f3ae2a0f-e6ba-4ee1-a0b9-75d7431ff32b\templates\extract_data.liquid">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="data\agents\f3ae2a0f-e6ba-4ee1-a0b9-75d7431ff32b\templates\html_parser.liquid">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>
<ItemGroup>

View file

@ -57,7 +57,8 @@ public class PlaywrightInstance : IDisposable
}
else
{
string userDataDir = args.UserDataDir ?? $"{Path.GetTempPath()}\\playwright\\{ctxId}";
string localAppDataFolder = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
string userDataDir = args.UserDataDir ?? $"{localAppDataFolder}\\playwright\\{ctxId}";
_contexts[ctxId] = await _playwright.Chromium.LaunchPersistentContextAsync(userDataDir, new BrowserTypeLaunchPersistentContextOptions
{
Headless = args.Headless,

View file

@ -15,9 +15,12 @@ public partial class PlaywrightWebDriver
if (page != null)
{
await page.EvaluateAsync(@"() => {
window.open('', '_blank');
}");
if (page.Url != "about:blank")
{
await page.EvaluateAsync(@"() => {
window.open('', '_blank');
}");
}
if (args.EnableResponseCallback)
{
@ -36,13 +39,6 @@ public partial class PlaywrightWebDriver
else
{
page = await _instance.NewPage(message, args);
Serilog.Log.Information($"goto page: {args.Url}");
if (args.OpenNewTab && page != null && page.Url == "about:blank")
{
page = await _instance.NewPage(message, args);
}
}
if (page == null)
@ -95,6 +91,7 @@ public partial class PlaywrightWebDriver
}
result.ResponseStatusCode = response.Status;
result.UrlAfterAction = page.Url;
if (response.Status == 200)
{
result.IsSuccess = true;

View file

@ -78,4 +78,9 @@ public partial class PlaywrightWebDriver : IWebBrowser
await page.Keyboard.PressAsync(key);
}
}
public async Task<bool> IsBrowserClosed(string contextId)
{
return !_instance.Contexts.ContainsKey(contextId);
}
}