Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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();
}
Original file line number Diff line number Diff line change
@@ -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; }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
namespace BotSharp.Abstraction.Chart.Models;

public class ChartCodeResult
{
public string Code { get; set; }
public string Language { get; set; }
}
Original file line number Diff line number Diff line change
@@ -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; }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
namespace BotSharp.Abstraction.Chart.Models;

public class ChartDataResult
{
public object Data { get; set; }
}
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using BotSharp.Abstraction.Chart;
using BotSharp.Abstraction.Files.Constants;
using BotSharp.Abstraction.Files.Enums;
using BotSharp.Abstraction.Files.Utilities;
Expand Down Expand Up @@ -530,6 +531,35 @@ public IActionResult DownloadMessageFile([FromRoute] string conversationId, [Fro
}
#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)
Expand Down
1 change: 1 addition & 0 deletions src/Infrastructure/BotSharp.OpenAPI/Using.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
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;
Expand Down
Original file line number Diff line number Diff line change
@@ -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";
}
Original file line number Diff line number Diff line change
@@ -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
};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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.


Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>$(TargetFramework)</TargetFramework>
Expand Down
Original file line number Diff line number Diff line change
@@ -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; }
}
155 changes: 155 additions & 0 deletions src/Plugins/BotSharp.Plugin.SqlDriver/Services/SqlChartService.cs
Original file line number Diff line number Diff line change
@@ -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);
}
}
1 change: 1 addition & 0 deletions src/Plugins/BotSharp.Plugin.SqlDriver/SqlDriverPlugin.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,5 +30,6 @@ public void RegisterDI(IServiceCollection services, IConfiguration config)
services.AddScoped<IConversationHook, SqlDriverConversationHook>();
services.AddScoped<IAgentUtilityHook, SqlUtilityHook>();
services.AddScoped<ICrontabHook, SqlDriverCrontabHook>();
services.AddScoped<IBotSharpChartService, SqlChartService>();
}
}
2 changes: 2 additions & 0 deletions src/Plugins/BotSharp.Plugin.SqlDriver/Using.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@
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;
Expand Down
Loading
Loading