Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
f54deb2
added a definition DTO for ai studio chat block diagrams
nilskruthoff Aug 4, 2026
191be98
developed a JSON contract for charts and injected it into the main sy…
nilskruthoff Aug 4, 2026
6f79c38
included a parser for JSON blocks that start with ` ```aistudio-chart `
nilskruthoff Aug 4, 2026
d975b31
added a component that renders the parsed JSON chart into an MudBlazo…
nilskruthoff Aug 4, 2026
05412eb
enabling chart output
nilskruthoff Aug 4, 2026
68866ec
extracting the raw JSON from the AI response and make sure its not a …
nilskruthoff Aug 4, 2026
9d61be3
render structured chart blocks in AI responses
nilskruthoff Aug 4, 2026
79c5102
adding the system prompt hints to documentation of assistant plugins …
nilskruthoff Aug 4, 2026
ef3e043
i18n
nilskruthoff Aug 4, 2026
cd14f1d
added a modern css stylesheet for all diagrams, resembles styling of …
nilskruthoff Aug 4, 2026
cfe117a
applied styles to the ChartBlock.razor
nilskruthoff Aug 4, 2026
05ede0c
included heatmaps and time series charts to the definition
nilskruthoff Aug 10, 2026
0af3901
parsing time zones and new chart types
nilskruthoff Aug 10, 2026
e6ed912
mention heatmaps and timezones and their specific rules in the releva…
nilskruthoff Aug 10, 2026
b22174b
included the heatmap and time series block to the chart rendering; sp…
nilskruthoff Aug 10, 2026
048be26
workaround heatmap not centered bug or taking too less space
nilskruthoff Aug 10, 2026
319ca88
Merge branch 'main' into chart-generation
nilskruthoff Aug 10, 2026
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
4 changes: 3 additions & 1 deletion app/MindWork AI Studio/Assistants/AssistantBase.razor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -339,6 +339,7 @@ protected void CreateChatThread()
this.ChatThread = new()
{
IncludeDateTime = false,
AllowChartOutput = true,
SelectedProvider = this.ProviderSettings.Id,
SelectedProfile = this.AllowProfiles ? this.CurrentProfile.Id : Profile.NO_PROFILE.Id,
SystemPrompt = this.SystemPrompt,
Expand All @@ -355,6 +356,7 @@ protected Guid CreateChatThread(Guid workspaceId, string name)
this.ChatThread = new()
{
IncludeDateTime = false,
AllowChartOutput = true,
SelectedProvider = this.ProviderSettings.Id,
SelectedProfile = this.AllowProfiles ? this.CurrentProfile.Id : Profile.NO_PROFILE.Id,
SystemPrompt = this.SystemPrompt,
Expand Down Expand Up @@ -922,4 +924,4 @@ private void ImportAssistantSessionState(IReadOnlyDictionary<string, IAssistantS
protected virtual void RestoreCustomAssistantSessionState(AssistantSessionStateReader state) { }

#endregion
}
}
3 changes: 3 additions & 0 deletions app/MindWork AI Studio/Assistants/I18N/allTexts.lua
Original file line number Diff line number Diff line change
Expand Up @@ -2797,6 +2797,9 @@ UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::VISUALBRIEFING::VISUALBRIEFINGFAILUREEXTE
-- The model response used an unsupported contract version. Please try again or select another model.
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::VISUALBRIEFING::VISUALBRIEFINGFAILUREEXTENSIONS::T921285247"] = "The model response used an unsupported contract version. Please try again or select another model."

-- This chart cannot be displayed: {0}
UI_TEXT_CONTENT["AISTUDIO::CHAT::CHARTBLOCK::T1070038198"] = "This chart cannot be displayed: {0}"

-- System
UI_TEXT_CONTENT["AISTUDIO::CHAT::CHATROLEEXTENSIONS::T2402387132"] = "System"

Expand Down
61 changes: 61 additions & 0 deletions app/MindWork AI Studio/Chat/ChartBlock.razor
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
@namespace AIStudio.Chat
@using MudBlazor
@inherits AIStudio.Components.MSGComponentBase

@if (this.Result.Chart is { } chart)
{
<div class="chart-block-shell my-3">
<MudPaper Class="chart-block" Outlined="true">
<MudText Typo="Typo.h6" Class="chart-block-title">@chart.Title</MudText>
@if (chart.Type is ChartDefinitionType.TIME_SERIES)
{
<div class="chart-block-plot chart-block-plot-axis">
<MudTimeSeriesChart ChartSeries="@this.TimeSeriesChartSeries"
ChartOptions="@this.ChartOptions"
AxisChartOptions="@this.AxisChartOptions"
TimeLabelSpacing="@this.TimeLabelSpacing"
TimeLabelFormat="@this.TimeLabelFormat"
DataMarkerTooltipTimeLabelFormat="dd-MM-yyyy HH:mm:ss 'UTC'"
Width="100%"
Height="350px" />
</div>
}
else if (chart.Type is ChartDefinitionType.PIE or ChartDefinitionType.DONUT)
{
<div class="chart-block-plot chart-block-plot-circular">
<MudChart ChartType="@this.ChartType"
InputData="@chart.Series[0].Values.ToArray()"
InputLabels="@chart.Categories.ToArray()"
ChartOptions="@this.ChartOptions"
Width="100%"
Height="200px" />
</div>
}
else
{
<div class="chart-block-plot chart-block-plot-axis">
<MudChart ChartType="@this.ChartType"
ChartSeries="@this.ChartSeries"
XAxisLabels="@chart.Categories.ToArray()"
ChartOptions="@this.CategoryChartOptions"
AxisChartOptions="@this.AxisChartOptions"
Width="100%"
Height="350px" />
</div>
}
@if (chart.Caption is not null)
{
<MudText Typo="Typo.caption" Align="Align.Center" Class="chart-block-caption d-block">
<em>@chart.Caption</em>
</MudText>
}
</MudPaper>
</div>
}
else
{
<MudAlert Severity="Severity.Warning" Variant="Variant.Outlined" Class="my-3">
@string.Format(T("This chart cannot be displayed: {0}"), this.Result.Error)
</MudAlert>
<pre class="overflow-auto"><code>@this.Result.RawJson</code></pre>
}
99 changes: 99 additions & 0 deletions app/MindWork AI Studio/Chat/ChartBlock.razor.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
using System.Globalization;

using AIStudio.Components;
using Microsoft.AspNetCore.Components;

namespace AIStudio.Chat;

public partial class ChartBlock : MSGComponentBase
{
private AxisChartOptions AxisChartOptions { get; } = new() { MatchBoundsToSize = true };

private ChartOptions ChartOptions { get; } = new()
{
ChartPalette =
[
"#236A50", "#F2D264", "#79AE90", "#C97857", "#4E7894", "#9B6B8F",
"#6A233D", "#6484F2", "#AE7997", "#57A8C9", "#946A4E", "#6B9B77",
],
};

private ChartOptions HeatMapChartOptions { get; } = new()
{
ChartPalette = ["#236A50", "#79AE90", "#F2D264", "#C97857", "#6A233D"],
EnableSmoothGradient = true,
YAxisLabelPosition = YAxisLabelPosition.Right,

};

[Parameter]
public ChartBlockParseResult Result { get; set; } = ChartBlockParseResult.Invalid(string.Empty, string.Empty);

private ChartType ChartType => this.Result.Chart?.Type switch
{
ChartDefinitionType.BAR => ChartType.Bar,
ChartDefinitionType.STACKED_BAR => ChartType.StackedBar,
ChartDefinitionType.LINE => ChartType.Line,
ChartDefinitionType.PIE => ChartType.Pie,
ChartDefinitionType.DONUT => ChartType.Donut,
ChartDefinitionType.HEATMAP => ChartType.HeatMap,
_ => ChartType.Bar,
};

private List<ChartSeries> ChartSeries => this.Result.Chart?.Series
.Select(series => new ChartSeries { Name = series.Name, Data = series.Values.ToArray() })
.ToList() ?? [];

private ChartOptions CategoryChartOptions => this.Result.Chart?.Type is ChartDefinitionType.HEATMAP
? this.HeatMapChartOptions
: this.ChartOptions;

private List<TimeSeriesChartSeries> TimeSeriesChartSeries => this.Result.Chart is not { } chart
? []
: chart.Series
.Select(series => new TimeSeriesChartSeries
{
Name = series.Name,
Data = chart.Categories
.Select((category, index) => new TimeSeriesChartSeries.TimeValue(
DateTimeOffset.Parse(category, CultureInfo.InvariantCulture).UtcDateTime,
series.Values[index]))
.ToList(),
IsVisible = true,
})
.ToList();

private TimeSpan TimeLabelSpacing
{
get
{
var timestamps = this.GetTimeSeriesTimestamps();
if (timestamps.Count < 2)
return TimeSpan.FromSeconds(1);

var range = timestamps[^1] - timestamps[0];
var intervalCount = Math.Min(timestamps.Count - 1, 8);
return TimeSpan.FromTicks(Math.Max(TimeSpan.TicksPerSecond, range.Ticks / intervalCount));
}
}

private string TimeLabelFormat
{
get
{
var timestamps = this.GetTimeSeriesTimestamps();
if (timestamps.Count < 2)
return "yyyy-MM-dd HH:mm";

var range = timestamps[^1] - timestamps[0];
if (range <= TimeSpan.FromDays(2))
return "MM-dd HH:mm";

return range <= TimeSpan.FromDays(730) ? "yyyy-MM-dd" : "yyyy";
}
}

private List<DateTimeOffset> GetTimeSeriesTimestamps() => this.Result.Chart?.Categories
.Select(category => DateTimeOffset.Parse(category, CultureInfo.InvariantCulture).ToUniversalTime())
.ToList() ?? [];
}
55 changes: 55 additions & 0 deletions app/MindWork AI Studio/Chat/ChartBlock.razor.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
.chart-block-shell {
--chart-pine: #236a50;
--chart-sage: #79ae90;
--chart-sun: #f2d264;
--chart-mist: #eaf1ec;
padding: 0 1.5rem;
}

.chart-block-shell ::deep .chart-block {
position: relative;
overflow: hidden;
padding: clamp(1rem, 2.5vw, 1.5rem);
border-color: color-mix(in srgb, var(--mud-palette-lines-default) 85%, var(--chart-pine));
border-radius: 1.25rem;
background: var(--mud-palette-surface);
background: color-mix(in srgb, var(--mud-palette-surface) 96%, var(--chart-mist));
box-shadow: 0 18px 55px rgba(22, 75, 59, .07);
}

.chart-block-shell ::deep .chart-block::before {
position: absolute;
inset: 0 0 auto;
height: .25rem;
background: linear-gradient(90deg, var(--chart-pine), var(--chart-sage), var(--chart-sun));
content: "";
}

.chart-block-shell ::deep .chart-block-title {
margin-block-end: 1rem;
font-weight: 700;
letter-spacing: -.02em;
}

.chart-block-plot {
width: 100%;
margin-inline: auto;
}

.chart-block-plot-circular {
max-width: 30rem;
}

.chart-block-plot-axis {
max-width: 56rem;
}

.chart-block-plot-heatmap {
max-width: none;
}

.chart-block-shell ::deep .chart-block-caption {
margin-block-start: .75rem;
color: var(--mud-palette-text-secondary);
line-height: 1.5;
}
Loading