add chart data and code endpoints
This commit is contained in:
parent
36bbfa8847
commit
9b30898f06
|
|
@ -0,0 +1,14 @@
|
|||
using BotSharp.Abstraction.Chart.Models;
|
||||
|
||||
namespace BotSharp.Abstraction.Chart;
|
||||
|
||||
public interface IBotSharpChartService
|
||||
{
|
||||
public string Provider { get; }
|
||||
|
||||
Task<ChartDataResult?> GetConversationChartData(string conversationId, string messageId, ChartDataOptions options)
|
||||
=> throw new NotImplementedException();
|
||||
|
||||
Task<ChartCodeResult?> GetConversationChartCode(string conversationId, string messageId, ChartCodeOptions options)
|
||||
=> throw new NotImplementedException();
|
||||
}
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
namespace BotSharp.Abstraction.Chart.Models;
|
||||
|
||||
public class ChartCodeOptions
|
||||
{
|
||||
public string? AgentId { get; set; }
|
||||
public string? TemplateName { get; set; }
|
||||
public string Text { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Conversation state that can be used to fetch chart data
|
||||
/// </summary>
|
||||
public string? TargetStateName { get; set; }
|
||||
|
||||
public ChartLlmOptions? Llm { get; set; }
|
||||
public List<KeyValue<object>>? States { get; set; }
|
||||
|
||||
}
|
||||
|
||||
public class ChartLlmOptions
|
||||
{
|
||||
public string? Provider { get; set; }
|
||||
public string? Model { get; set; }
|
||||
public int? MaxOutputTokens { get; set; }
|
||||
public string? ReasoningEffortLevel { get; set; }
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
namespace BotSharp.Abstraction.Chart.Models;
|
||||
|
||||
public class ChartCodeResult
|
||||
{
|
||||
public string Code { get; set; }
|
||||
public string Language { get; set; }
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
namespace BotSharp.Abstraction.Chart.Models;
|
||||
|
||||
public class ChartDataOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// Conversation state that can be used to fetch chart data
|
||||
/// </summary>
|
||||
public string? TargetStateName { get; set; }
|
||||
}
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
namespace BotSharp.Abstraction.Chart.Models;
|
||||
|
||||
public class ChartDataResult
|
||||
{
|
||||
public object Data { get; set; }
|
||||
}
|
||||
|
|
@ -1,3 +1,4 @@
|
|||
using BotSharp.Abstraction.Chart;
|
||||
using BotSharp.Abstraction.Files.Constants;
|
||||
using BotSharp.Abstraction.Files.Enums;
|
||||
using BotSharp.Abstraction.Files.Utilities;
|
||||
|
|
@ -530,6 +531,35 @@ public class ConversationController : ControllerBase
|
|||
}
|
||||
#endregion
|
||||
|
||||
#region Chart
|
||||
[AllowAnonymous]
|
||||
[HttpGet("/conversation/{conversationId}/message/{messageId}/user/chart/data")]
|
||||
public async Task<ConversationChartDataResponse?> GetConversationChartData(
|
||||
[FromRoute] string conversationId,
|
||||
[FromRoute] string messageId,
|
||||
[FromQuery] ConversationChartDataRequest request)
|
||||
{
|
||||
var chart = _services.GetServices<IBotSharpChartService>().FirstOrDefault(x => x.Provider == request?.ChartProvider);
|
||||
if (chart == null) return null;
|
||||
|
||||
var result = await chart.GetConversationChartData(conversationId, messageId, request);
|
||||
return ConversationChartDataResponse.From(result);
|
||||
}
|
||||
|
||||
[HttpPost("/conversation/{conversationId}/message/{messageId}/user/chart/code")]
|
||||
public async Task<ConversationChartCodeResponse?> GetConversationChartCode(
|
||||
[FromRoute] string conversationId,
|
||||
[FromRoute] string messageId,
|
||||
[FromBody] ConversationChartCodeRequest request)
|
||||
{
|
||||
var chart = _services.GetServices<IBotSharpChartService>().FirstOrDefault(x => x.Provider == request?.ChartProvider);
|
||||
if (chart == null) return null;
|
||||
|
||||
var result = await chart.GetConversationChartCode(conversationId, messageId, request);
|
||||
return ConversationChartCodeResponse.From(result);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Dashboard
|
||||
[HttpPut("/agent/{agentId}/conversation/{conversationId}/dashboard")]
|
||||
public async Task<bool> PinConversationToDashboard([FromRoute] string agentId, [FromRoute] string conversationId)
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ global using BotSharp.Abstraction.Files.Models;
|
|||
global using BotSharp.Abstraction.Files;
|
||||
global using BotSharp.Abstraction.VectorStorage.Enums;
|
||||
global using BotSharp.Abstraction.Knowledges.Models;
|
||||
global using BotSharp.Abstraction.Chart.Models;
|
||||
global using BotSharp.OpenAPI.ViewModels.Conversations;
|
||||
global using BotSharp.OpenAPI.ViewModels.Users;
|
||||
global using BotSharp.OpenAPI.ViewModels.Agents;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,17 @@
|
|||
namespace BotSharp.OpenAPI.ViewModels.Conversations;
|
||||
|
||||
public class ConversationChartDataRequest : ChartDataOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// Chart service provider
|
||||
/// </summary>
|
||||
public string ChartProvider { get; set; } = "Botsharp";
|
||||
}
|
||||
|
||||
public class ConversationChartCodeRequest : ChartCodeOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// Chart service provider
|
||||
/// </summary>
|
||||
public string ChartProvider { get; set; } = "Botsharp";
|
||||
}
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
namespace BotSharp.OpenAPI.ViewModels.Conversations;
|
||||
|
||||
public class ConversationChartDataResponse
|
||||
{
|
||||
public object Data { get; set; }
|
||||
|
||||
public static ConversationChartDataResponse? From(ChartDataResult? result)
|
||||
{
|
||||
if (result == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return new()
|
||||
{
|
||||
Data = result.Data
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public class ConversationChartCodeResponse
|
||||
{
|
||||
public string Code { get; set; }
|
||||
public string Language { get; set; }
|
||||
|
||||
public static ConversationChartCodeResponse? From(ChartCodeResult? result)
|
||||
{
|
||||
if (result == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return new()
|
||||
{
|
||||
Code = result.Code,
|
||||
Language = result.Language
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -7,23 +7,24 @@ You must strictly follow the "Hard Requirements", "Render Requirements", "Code R
|
|||
|
||||
***** Hard Requirements *****
|
||||
** Your output javascript code must be wrapped in one or multiple <script>...</script> blocks with everything needed inside.
|
||||
** You need to import ECharts.js exactly once to plot the charts. The script source is "https://cdnjs.cloudflare.com/ajax/libs/echarts/5.5.1/echarts.min.js".
|
||||
** You need to import ECharts.js exactly once to plot the charts. The script source is "https://cdnjs.cloudflare.com/ajax/libs/echarts/6.0.0/echarts.min.js".
|
||||
** You must add an ECharts Toolbox bar at the top left corner of the chart.
|
||||
** ALWAYS add a "Full screen" button right next to the Toolbox bar.
|
||||
** The "Full screen" button can toggle the chart container in and out of fullscreen using the Fullscreen API. Requirements for this button:
|
||||
* Always call the Fullscreen API on the chart container div itself (document.getElementById("{{ chart_element_id }}")), not on the document.
|
||||
* When entering fullscreen, the chart container must have widhth: 100vw and height: 100vh.
|
||||
* Use el.requestFullscreen() with fallbacks to el.webkitRequestFullscreen || el.msRequestFullscreen.
|
||||
* Exit fullscreen with document.exitFullscreen() and vendor fallbacks.
|
||||
* Listen for fullscreenchange, webkitfullscreenchange, and msfullscreenchange to keep the button working across repeated clicks and ESC exits.
|
||||
* Ensure the chart fully expands and scales to the entire screen when fullscreen is active.
|
||||
* fullscreenBtn must be a fully-formed object {show: true, name, title, icon: 'path://M3 3 H9 V5 H5 V9 H3 Z M15 3 H21 V9 H19 V5 H15 Z M3 15 H5 V19 H9 V21 H3 Z M19 15 H21 V21 H15 V19 H19 Z', onclick}.
|
||||
* When using "chart.setOption" to define the fullscreen button, DO NOT use "graphic". Include the fullscreenBtn object in toolbox.feature with name 'myFullscreen'.
|
||||
** Initialize the chart with explicit non-zero width (at least 800px) and non-zero height (at least 500px).
|
||||
|
||||
|
||||
***** Render Requirements *****
|
||||
** You must render the charts under the div html element with id {{ chart_element_id }}.
|
||||
** You must not create any new html element.
|
||||
** You must ensure the generated charts have visible height (at least 500px) and width (at least 800px). DO NOT generate charts with zero height or zero width.
|
||||
** You must not apply any styles on any html element.
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>$(TargetFramework)</TargetFramework>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,18 @@
|
|||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace BotSharp.Plugin.SqlDriver.LlmContext;
|
||||
|
||||
internal class ChartLlmContextOut
|
||||
{
|
||||
[JsonPropertyName("greeting_message")]
|
||||
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
||||
public string? GreetingMessage { get; set; }
|
||||
|
||||
[JsonPropertyName("report_summary")]
|
||||
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
||||
public string? ReportSummary { get; set; }
|
||||
|
||||
[JsonPropertyName("js_code")]
|
||||
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
||||
public string? JsCode { get; set; }
|
||||
}
|
||||
|
|
@ -0,0 +1,155 @@
|
|||
using BotSharp.Abstraction.Options;
|
||||
using BotSharp.Abstraction.Repositories;
|
||||
using BotSharp.Core.Infrastructures;
|
||||
using BotSharp.Plugin.SqlDriver.LlmContext;
|
||||
|
||||
namespace BotSharp.Plugin.SqlDriver.Services;
|
||||
|
||||
public class SqlChartService : IBotSharpChartService
|
||||
{
|
||||
private readonly IServiceProvider _services;
|
||||
private readonly ILogger<SqlChartService> _logger;
|
||||
private readonly BotSharpOptions _botSharpOptions;
|
||||
|
||||
public SqlChartService(
|
||||
IServiceProvider services,
|
||||
ILogger<SqlChartService> logger,
|
||||
BotSharpOptions botSharpOptions)
|
||||
{
|
||||
_services = services;
|
||||
_logger = logger;
|
||||
_botSharpOptions = botSharpOptions;
|
||||
}
|
||||
|
||||
public string Provider => "sql_driver";
|
||||
|
||||
public async Task<ChartDataResult?> GetConversationChartData(string conversationId, string messageId, ChartDataOptions options)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(conversationId))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(options?.TargetStateName))
|
||||
{
|
||||
var db = _services.GetRequiredService<IBotSharpRepository>();
|
||||
var states = db.GetConversationStates(conversationId);
|
||||
var value = states?.GetValueOrDefault(options?.TargetStateName)?.Values?.LastOrDefault()?.Data;
|
||||
|
||||
// To do
|
||||
//return new ChartDataResult();
|
||||
}
|
||||
|
||||
// Dummy data for testing
|
||||
var data = new
|
||||
{
|
||||
categories = new string[] { "A", "B", "C", "D", "E" },
|
||||
values = new int[] { 42, 67, 29, 85, 53 }
|
||||
};
|
||||
|
||||
return new ChartDataResult { Data = data };
|
||||
}
|
||||
|
||||
public async Task<ChartCodeResult?> GetConversationChartCode(string conversationId, string messageId, ChartCodeOptions options)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(conversationId))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var agentService = _services.GetRequiredService<IAgentService>();
|
||||
|
||||
var agentId = options.AgentId.IfNullOrEmptyAs(BuiltInAgentId.UtilityAssistant);
|
||||
var templateName = options.TemplateName.IfNullOrEmptyAs("util-chart-plot_instruction");
|
||||
var inst = GetChartCodeInstruction(agentId, templateName);
|
||||
|
||||
var agent = await agentService.GetAgent(agentId);
|
||||
agent = new Agent
|
||||
{
|
||||
Id = agent.Id,
|
||||
Name = agent.Name,
|
||||
Instruction = inst,
|
||||
LlmConfig = new AgentLlmConfig
|
||||
{
|
||||
MaxOutputTokens = options.Llm?.MaxOutputTokens ?? 8192,
|
||||
ReasoningEffortLevel = options.Llm?.ReasoningEffortLevel
|
||||
},
|
||||
TemplateDict = BuildChartStates(options)
|
||||
};
|
||||
|
||||
var dialogs = new List<RoleDialogModel>
|
||||
{
|
||||
new RoleDialogModel
|
||||
{
|
||||
Role = AgentRole.User,
|
||||
MessageId = messageId,
|
||||
Content = options.Text.IfNullOrEmptyAs("Please follow the instruction to generate response.")
|
||||
}
|
||||
};
|
||||
var response = await GetChatCompletion(agent, dialogs, options);
|
||||
var obj = response.JsonContent<ChartLlmContextOut>();
|
||||
|
||||
return new ChartCodeResult
|
||||
{
|
||||
Code = obj?.JsCode,
|
||||
Language = "javascript"
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
private Dictionary<string, object> BuildChartStates(ChartCodeOptions options)
|
||||
{
|
||||
var states = new Dictionary<string, object>();
|
||||
|
||||
if (!options.States.IsNullOrEmpty())
|
||||
{
|
||||
foreach (var item in options.States)
|
||||
{
|
||||
if (item.Value == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
states[item.Key] = item.Value;
|
||||
}
|
||||
}
|
||||
return states;
|
||||
}
|
||||
|
||||
private string GetChartCodeInstruction(string agentId, string templateName)
|
||||
{
|
||||
var db = _services.GetRequiredService<IBotSharpRepository>();
|
||||
var templateContent = db.GetAgentTemplate(agentId, templateName);
|
||||
return templateContent;
|
||||
}
|
||||
|
||||
private async Task<string> GetChatCompletion(Agent agent, List<RoleDialogModel> dialogs, ChartCodeOptions options)
|
||||
{
|
||||
try
|
||||
{
|
||||
var (provider, model) = GetLlmProviderModel(options);
|
||||
var completion = CompletionProvider.GetChatCompletion(_services, provider: provider, model: model);
|
||||
var response = await completion.GetChatCompletions(agent, dialogs);
|
||||
return response.Content;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
var error = $"Error when generating chart code. {ex.Message}";
|
||||
_logger.LogWarning(ex, error);
|
||||
return error;
|
||||
}
|
||||
}
|
||||
|
||||
private (string, string) GetLlmProviderModel(ChartCodeOptions options)
|
||||
{
|
||||
var provider = "openai";
|
||||
var model = "gpt-5";
|
||||
|
||||
if (options?.Llm != null)
|
||||
{
|
||||
provider = options.Llm.Provider.IfNullOrEmptyAs(provider);
|
||||
model = options.Llm.Model.IfNullOrEmptyAs(model);
|
||||
}
|
||||
|
||||
return (provider, model);
|
||||
}
|
||||
}
|
||||
|
|
@ -30,5 +30,6 @@ public class SqlDriverPlugin : IBotSharpPlugin
|
|||
services.AddScoped<IConversationHook, SqlDriverConversationHook>();
|
||||
services.AddScoped<IAgentUtilityHook, SqlUtilityHook>();
|
||||
services.AddScoped<ICrontabHook, SqlDriverCrontabHook>();
|
||||
services.AddScoped<IBotSharpChartService, SqlChartService>();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,6 +25,8 @@ global using BotSharp.Abstraction.Agents.Enums;
|
|||
global using BotSharp.Abstraction.Instructs;
|
||||
global using BotSharp.Abstraction.Instructs.Models;
|
||||
global using BotSharp.Abstraction.Routing;
|
||||
global using BotSharp.Abstraction.Chart;
|
||||
global using BotSharp.Abstraction.Chart.Models;
|
||||
|
||||
global using BotSharp.Plugin.SqlDriver.Models;
|
||||
global using BotSharp.Plugin.SqlDriver.Hooks;
|
||||
|
|
|
|||
|
|
@ -549,6 +549,7 @@
|
|||
"BotSharp.Plugin.EmailHandler",
|
||||
"BotSharp.Plugin.AudioHandler",
|
||||
"BotSharp.Plugin.ChartHandler",
|
||||
"BotSharp.Plugin.SqlDriver",
|
||||
"BotSharp.Plugin.TencentCos"
|
||||
]
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue