2023-10-12 11:30:13 +00:00
|
|
|
using BotSharp.Abstraction.Models;
|
2023-08-28 03:50:10 +00:00
|
|
|
using BotSharp.Abstraction.Routing.Models;
|
2023-08-25 15:33:21 +00:00
|
|
|
using BotSharp.Abstraction.Templating;
|
2024-05-20 17:54:38 +00:00
|
|
|
using BotSharp.Abstraction.Translation.Models;
|
2023-08-25 15:33:21 +00:00
|
|
|
using Fluid;
|
2025-04-28 19:53:32 +00:00
|
|
|
using Fluid.Ast;
|
2025-07-22 15:26:00 +00:00
|
|
|
using Fluid.Values;
|
2024-11-25 22:13:40 +00:00
|
|
|
using System.Collections;
|
2025-05-05 04:20:52 +00:00
|
|
|
using System.IO;
|
2024-11-25 22:13:40 +00:00
|
|
|
using System.Reflection;
|
2025-05-05 04:20:52 +00:00
|
|
|
using System.Text.Encodings.Web;
|
2025-07-22 04:01:53 +00:00
|
|
|
using System.Text.RegularExpressions;
|
2023-08-25 15:33:21 +00:00
|
|
|
|
|
|
|
|
namespace BotSharp.Core.Templating;
|
|
|
|
|
|
|
|
|
|
public class TemplateRender : ITemplateRender
|
|
|
|
|
{
|
|
|
|
|
private readonly IServiceProvider _services;
|
|
|
|
|
private readonly ILogger _logger;
|
|
|
|
|
private static readonly FluidParser _parser = new FluidParser();
|
|
|
|
|
private TemplateOptions _options;
|
|
|
|
|
|
|
|
|
|
public TemplateRender(IServiceProvider services, ILogger<TemplateRender> logger)
|
|
|
|
|
{
|
|
|
|
|
_services = services;
|
|
|
|
|
_logger = logger;
|
|
|
|
|
_options = new TemplateOptions();
|
2023-08-28 03:50:10 +00:00
|
|
|
_options.MemberAccessStrategy.MemberNameStrategy = MemberNameStrategies.SnakeCase;
|
2023-10-12 11:30:13 +00:00
|
|
|
|
|
|
|
|
_options.MemberAccessStrategy.Register<NameDesc>();
|
2023-10-20 23:57:45 +00:00
|
|
|
_options.MemberAccessStrategy.Register<ParameterPropertyDef>();
|
2023-10-20 03:47:14 +00:00
|
|
|
_options.MemberAccessStrategy.Register<RoleDialogModel>();
|
2023-10-12 11:30:13 +00:00
|
|
|
_options.MemberAccessStrategy.Register<Agent>();
|
2024-01-24 23:02:59 +00:00
|
|
|
_options.MemberAccessStrategy.Register<RoutableAgent>();
|
2023-09-22 20:38:58 +00:00
|
|
|
_options.MemberAccessStrategy.Register<RoutingHandlerDef>();
|
2024-05-02 22:07:12 +00:00
|
|
|
_options.MemberAccessStrategy.Register<FunctionDef>();
|
|
|
|
|
_options.MemberAccessStrategy.Register<FunctionParametersDef>();
|
2024-03-07 18:27:29 +00:00
|
|
|
_options.MemberAccessStrategy.Register<UserIdentity>();
|
2024-05-20 17:54:38 +00:00
|
|
|
_options.MemberAccessStrategy.Register<TranslationInput>();
|
2025-05-05 04:20:52 +00:00
|
|
|
|
2025-07-22 15:26:00 +00:00
|
|
|
_options.Filters.AddFilter("from_agent", FromAgentFilter);
|
|
|
|
|
|
2025-07-22 04:01:53 +00:00
|
|
|
_parser.RegisterExpressionTag("link", (Expression expression, TextWriter writer, TextEncoder encoder, TemplateContext context) =>
|
2025-05-08 03:14:05 +00:00
|
|
|
{
|
2025-07-22 04:01:53 +00:00
|
|
|
return RenderTag("link", expression, writer, encoder, context, services);
|
2025-05-08 03:14:05 +00:00
|
|
|
});
|
2023-08-25 15:33:21 +00:00
|
|
|
}
|
|
|
|
|
|
2023-08-30 23:29:01 +00:00
|
|
|
public string Render(string template, Dictionary<string, object> dict)
|
2023-08-25 15:33:21 +00:00
|
|
|
{
|
|
|
|
|
if (_parser.TryParse(template, out var t, out var error))
|
|
|
|
|
{
|
|
|
|
|
var context = new TemplateContext(dict, _options);
|
2023-08-30 23:29:01 +00:00
|
|
|
template = t.Render(context);
|
2023-08-25 15:33:21 +00:00
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2023-10-28 20:59:26 +00:00
|
|
|
_logger.LogWarning(error);
|
2023-08-25 15:33:21 +00:00
|
|
|
}
|
2025-04-28 19:53:32 +00:00
|
|
|
|
|
|
|
|
return template;
|
2023-08-25 15:33:21 +00:00
|
|
|
}
|
2024-11-25 22:13:40 +00:00
|
|
|
|
2025-04-28 19:53:32 +00:00
|
|
|
public void RegisterType(Type type)
|
2024-11-25 22:13:40 +00:00
|
|
|
{
|
|
|
|
|
if (type == null || IsStringType(type)) return;
|
|
|
|
|
|
|
|
|
|
if (IsListType(type))
|
|
|
|
|
{
|
|
|
|
|
if (type.IsGenericType)
|
|
|
|
|
{
|
|
|
|
|
var genericType = type.GetGenericArguments()[0];
|
2025-04-28 19:53:32 +00:00
|
|
|
RegisterType(genericType);
|
2024-11-25 22:13:40 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else if (IsTrackToNextLevel(type))
|
|
|
|
|
{
|
|
|
|
|
_options.MemberAccessStrategy.Register(type);
|
|
|
|
|
var props = type.GetProperties();
|
|
|
|
|
foreach (var prop in props)
|
|
|
|
|
{
|
2025-04-28 19:53:32 +00:00
|
|
|
RegisterType(prop.PropertyType);
|
2024-11-25 22:13:40 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#region Private methods
|
2025-07-22 04:01:53 +00:00
|
|
|
private static async ValueTask<Completion> RenderTag(
|
|
|
|
|
string tag,
|
|
|
|
|
Expression expression,
|
|
|
|
|
TextWriter writer,
|
|
|
|
|
TextEncoder encoder,
|
|
|
|
|
TemplateContext context,
|
|
|
|
|
IServiceProvider services)
|
2025-05-05 04:20:52 +00:00
|
|
|
{
|
|
|
|
|
try
|
|
|
|
|
{
|
2025-07-22 04:01:53 +00:00
|
|
|
var value = await expression.EvaluateAsync(context);
|
|
|
|
|
var expStr = value?.ToStringValue() ?? string.Empty;
|
|
|
|
|
|
|
|
|
|
value = await context.Model.GetValueAsync(TemplateRenderConstant.RENDER_AGENT, context);
|
2025-05-05 04:20:52 +00:00
|
|
|
var agent = value?.ToObjectValue() as Agent;
|
|
|
|
|
|
2025-07-22 15:26:00 +00:00
|
|
|
var splited = Regex.Split(expStr, @"\s*from_agent\s*", RegexOptions.IgnoreCase | RegexOptions.CultureInvariant)
|
2025-07-22 04:01:53 +00:00
|
|
|
.Where(x => !string.IsNullOrWhiteSpace(x))
|
|
|
|
|
.Select(x => x.Trim())
|
|
|
|
|
.ToArray();
|
|
|
|
|
|
|
|
|
|
var templateName = splited.ElementAtOrDefault(0);
|
|
|
|
|
var agentName = splited.ElementAtOrDefault(1);
|
|
|
|
|
|
|
|
|
|
if (splited.Length > 1 && !agentName.IsEqualTo(agent?.Name))
|
|
|
|
|
{
|
|
|
|
|
using var scope = services.CreateScope();
|
|
|
|
|
var agentService = scope.ServiceProvider.GetRequiredService<IAgentService>();
|
|
|
|
|
var result = await agentService.GetAgents(new() { SimilarName = agentName });
|
|
|
|
|
agent = result?.Items?.FirstOrDefault();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var template = agent?.Templates?.FirstOrDefault(x => x.Name.IsEqualTo(templateName));
|
|
|
|
|
var key = $"{tag} | {agent?.Id} | {templateName}";
|
|
|
|
|
|
|
|
|
|
if (template == null || (context.AmbientValues.TryGetValue(key, out var visited) && (bool)visited))
|
2025-05-05 04:20:52 +00:00
|
|
|
{
|
|
|
|
|
writer.Write(string.Empty);
|
|
|
|
|
}
|
2025-07-22 04:01:53 +00:00
|
|
|
else if (_parser.TryParse(template.Content, out var t, out _))
|
2025-05-05 04:20:52 +00:00
|
|
|
{
|
|
|
|
|
context.AmbientValues[key] = true;
|
|
|
|
|
var rendered = t.Render(context);
|
|
|
|
|
writer.Write(rendered);
|
|
|
|
|
context.AmbientValues.Remove(key);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
writer.Write(string.Empty);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
catch
|
|
|
|
|
{
|
|
|
|
|
writer.Write(string.Empty);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return Completion.Normal;
|
|
|
|
|
}
|
|
|
|
|
|
2025-07-22 15:26:00 +00:00
|
|
|
private static ValueTask<FluidValue> FromAgentFilter(
|
|
|
|
|
FluidValue input,
|
|
|
|
|
FilterArguments arguments,
|
|
|
|
|
TemplateContext context)
|
|
|
|
|
{
|
|
|
|
|
var inputStr = input?.ToStringValue() ?? string.Empty;
|
|
|
|
|
var fromAgent = arguments.At(0).ToStringValue();
|
|
|
|
|
return new StringValue($"{inputStr} from_agent {fromAgent}");
|
|
|
|
|
}
|
|
|
|
|
|
2024-11-25 22:13:40 +00:00
|
|
|
private static bool IsStringType(Type type)
|
|
|
|
|
{
|
|
|
|
|
return type == typeof(string);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static bool IsListType(Type type)
|
|
|
|
|
{
|
|
|
|
|
var interfaces = type.GetTypeInfo().ImplementedInterfaces;
|
|
|
|
|
return type.IsArray || interfaces.Any(x => x.Name == typeof(IEnumerable).Name);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static bool IsTrackToNextLevel(Type type)
|
|
|
|
|
{
|
|
|
|
|
return type.IsClass || type.IsInterface || type.IsAbstract;
|
|
|
|
|
}
|
|
|
|
|
#endregion
|
2023-08-25 15:33:21 +00:00
|
|
|
}
|