Improve settings logs agent and renderer
PR and Push Build/Test / build-and-test (push) Successful in 8m13s

This commit is contained in:
2026-06-02 00:09:03 +02:00
parent 11c65ce669
commit e9825f5bf5
11 changed files with 1507 additions and 527 deletions
@@ -1,128 +1,150 @@
#if WINDOWS
using Aprillz.MewUI;
using Aprillz.MewUI.Controls;
using System.Diagnostics;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using static MeetingAssistant.Workflow.WorkflowRulesEditorWpfTheme;
namespace MeetingAssistant.Workflow;
internal static class WorkflowRulesEditorMarkdownRenderer
{
public static UIElement CreateContent(string content)
public static UIElement CreateContent(
string content,
WorkflowRulesEditorMarkdownLinkResolver? linkResolver = null)
{
var panel = new StackPanel()
.Vertical()
.Spacing(7);
foreach (var block in WorkflowRulesEditorMarkdown.Parse(content))
linkResolver ??= WorkflowRulesEditorMarkdownLinkResolver.Empty;
var panel = new StackPanel
{
panel.Add(block switch
Orientation = Orientation.Vertical
};
var blocks = WorkflowRulesEditorMarkdown.Parse(content);
for (var index = 0; index < blocks.Count; index++)
{
var element = blocks[index] switch
{
WorkflowRulesEditorMarkdownHeading heading => CreateHeading(heading, linkResolver),
WorkflowRulesEditorMarkdownBlockQuote quote => CreateBlockQuote(quote, linkResolver),
WorkflowRulesEditorMarkdownImage image => CreateImageBlock(image, linkResolver),
WorkflowRulesEditorMarkdownTaskList tasks => CreateTaskList(tasks, linkResolver),
WorkflowRulesEditorMarkdownCodeBlock codeBlock => CreateCodeBlock(codeBlock),
WorkflowRulesEditorMarkdownTable table => CreateTable(table),
WorkflowRulesEditorMarkdownParagraph paragraph => CreateParagraph(paragraph),
WorkflowRulesEditorMarkdownParagraph paragraph => CreateParagraph(paragraph, linkResolver),
_ => new TextBlock()
});
};
if (index < blocks.Count - 1)
{
element.SetValue(FrameworkElement.MarginProperty, new Thickness(0, 0, 0, 7));
}
panel.Children.Add(element);
}
return panel;
}
private static UIElement CreateParagraph(WorkflowRulesEditorMarkdownParagraph paragraph)
private static UIElement CreateHeading(
WorkflowRulesEditorMarkdownHeading heading,
WorkflowRulesEditorMarkdownLinkResolver linkResolver)
{
var lines = SplitInlineLines(paragraph.Inlines);
if (lines.Count == 1)
{
return CreateParagraphLine(lines[0]);
}
var paragraph = CreateInlineParagraph(heading.Inlines, linkResolver, heading.Level == 1 ? 22 : 20);
paragraph.FontWeight = FontWeights.SemiBold;
var panel = new StackPanel()
.Vertical()
.Spacing(3);
foreach (var line in lines)
{
panel.Add(CreateParagraphLine(line));
}
return panel;
return CreateSelectableDocument([paragraph], fontSize: heading.Level == 1 ? 16.5 : 15.5);
}
private static UIElement CreateParagraphLine(IReadOnlyList<WorkflowRulesEditorMarkdownInline> inlines)
private static UIElement CreateBlockQuote(
WorkflowRulesEditorMarkdownBlockQuote quote,
WorkflowRulesEditorMarkdownLinkResolver linkResolver)
{
var linePanel = new WrapPanel()
.Spacing(1);
foreach (var inline in inlines)
var paragraph = CreateInlineParagraph(quote.Inlines, linkResolver);
return new Border
{
foreach (var element in CreateInlineElements(inline))
Padding = new Thickness(10, 7, 10, 7),
BorderThickness = new Thickness(3, 0, 0, 0),
BorderBrush = QuoteBorder,
Background = QuoteBackground,
Child = CreateSelectableDocument([paragraph])
};
}
private static UIElement CreateParagraph(
WorkflowRulesEditorMarkdownParagraph paragraph,
WorkflowRulesEditorMarkdownLinkResolver linkResolver)
{
var flowParagraph = CreateInlineParagraph(paragraph.Inlines, linkResolver);
return CreateSelectableDocument([flowParagraph]);
}
private static UIElement CreateImageBlock(
WorkflowRulesEditorMarkdownImage image,
WorkflowRulesEditorMarkdownLinkResolver linkResolver)
{
return CreateImagePreview(image.AltText, image.Target, linkResolver);
}
private static UIElement CreateTaskList(
WorkflowRulesEditorMarkdownTaskList tasks,
WorkflowRulesEditorMarkdownLinkResolver linkResolver)
{
var grid = new Grid();
grid.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(24) });
grid.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) });
for (var row = 0; row < tasks.Items.Count; row++)
{
var item = tasks.Items[row];
grid.RowDefinitions.Add(new RowDefinition { Height = GridLength.Auto });
var glyph = new TextBlock
{
linePanel.Add(element);
}
Text = item.IsChecked ? "☑" : "☐",
FontFamily = new FontFamily("Segoe UI Symbol"),
FontSize = 17,
Foreground = PrimaryText,
LineHeight = 19,
Margin = new Thickness(0, 0, 6, 3),
VerticalAlignment = VerticalAlignment.Top
};
Grid.SetRow(glyph, row);
Grid.SetColumn(glyph, 0);
grid.Children.Add(glyph);
var paragraph = CreateInlineParagraph(item.Inlines, linkResolver);
var text = CreateSelectableDocument([paragraph]);
text.Margin = new Thickness(0, 0, 0, 3);
Grid.SetRow(text, row);
Grid.SetColumn(text, 1);
grid.Children.Add(text);
}
return linePanel;
return grid;
}
private static IEnumerable<UIElement> CreateInlineElements(WorkflowRulesEditorMarkdownInline inline)
private static UIElement CreateCodeBlock(WorkflowRulesEditorMarkdownCodeBlock codeBlock)
{
if (inline.Style == WorkflowRulesEditorMarkdownInlineStyle.Code)
var run = new Run(codeBlock.Text)
{
yield return CreateInlineCode(inline.Text);
yield break;
}
var text = new TextBlock()
.Text(inline.Text)
.TextWrapping(TextWrapping.Wrap)
.Foreground(Color.FromRgb(230, 235, 243));
if (inline.Style == WorkflowRulesEditorMarkdownInlineStyle.Bold)
FontFamily = new FontFamily("Consolas")
};
var paragraph = CreateParagraphBlock();
paragraph.Inlines.Add(run);
var document = CreateSelectableDocument([paragraph], CodeText);
return new Border
{
text.FontWeight(FontWeight.Bold);
}
else if (inline.Style == WorkflowRulesEditorMarkdownInlineStyle.Italic)
{
text.FontFamily("Segoe UI Italic");
}
yield return text;
}
private static IReadOnlyList<IReadOnlyList<WorkflowRulesEditorMarkdownInline>> SplitInlineLines(
IReadOnlyList<WorkflowRulesEditorMarkdownInline> inlines)
{
var lines = new List<IReadOnlyList<WorkflowRulesEditorMarkdownInline>>();
var current = new List<WorkflowRulesEditorMarkdownInline>();
foreach (var inline in inlines)
{
var parts = inline.Text.Split('\n');
for (var index = 0; index < parts.Length; index++)
{
if (parts[index].Length > 0)
{
current.Add(inline with { Text = parts[index] });
}
if (index < parts.Length - 1)
{
lines.Add(current);
current = [];
}
}
}
lines.Add(current);
return lines;
}
private static UIElement CreateInlineCode(string text)
{
return new Border()
.Padding(4, 1, 4, 2)
.Margin(1, 0, 1, 0)
.CornerRadius(4)
.BorderThickness(1)
.BorderBrush(Color.FromRgb(86, 94, 108))
.Background(Color.FromRgb(24, 28, 35))
.Child(new TextBlock()
.Text(text)
.FontFamily("Consolas")
.Foreground(Color.FromRgb(237, 241, 247)));
Padding = new Thickness(9, 7, 9, 7),
CornerRadius = new CornerRadius(6),
BorderThickness = new Thickness(1),
BorderBrush = CodeBorder,
Background = CodeBackground,
Child = document
};
}
private static UIElement CreateTable(WorkflowRulesEditorMarkdownTable table)
@@ -130,46 +152,330 @@ internal static class WorkflowRulesEditorMarkdownRenderer
var rows = table.Rows
.Select(row => new MarkdownTableRow(PadCells(row, table.Header.Count)))
.ToArray();
var gridView = new GridView()
.HeaderHeight(30)
.RowHeight(30)
.CellPadding(new Thickness(8, 4, 8, 4))
.ShowGridLines(true)
.ZebraStriping(true);
for (var index = 0; index < table.Header.Count; index++)
var grid = new Grid();
grid.RowDefinitions.Add(new RowDefinition { Height = GridLength.Auto });
for (var column = 0; column < table.Header.Count; column++)
{
var columnIndex = index;
gridView.AddColumn<MarkdownTableRow>(
table.Header[index],
GetMarkdownTableColumnWidth(table, index),
_ => new TextBlock()
.TextWrapping(TextWrapping.NoWrap)
.Foreground(Color.FromRgb(230, 235, 243)),
(element, row, _, _) => ((TextBlock)element).Text(row.GetCell(columnIndex)),
minWidth: 64,
resizable: true);
grid.ColumnDefinitions.Add(new ColumnDefinition
{
Width = new GridLength(GetMarkdownTableColumnWidth(table, column))
});
AddTableCell(grid, 0, column, table.Header[column], TableHeaderBackground, FontWeights.SemiBold);
}
return gridView
.ItemsSource(rows)
.MinWidth(Math.Min(900, table.Header.Count * 120))
.Height(Math.Min(420, 30 + Math.Max(1, rows.Length) * 30));
for (var row = 0; row < rows.Length; row++)
{
grid.RowDefinitions.Add(new RowDefinition { Height = GridLength.Auto });
for (var column = 0; column < table.Header.Count; column++)
{
AddTableCell(grid, row + 1, column, rows[row].GetCell(column), TableCellBackground, FontWeights.Normal);
}
}
return new ScrollViewer
{
Content = grid,
HorizontalScrollBarVisibility = ScrollBarVisibility.Auto,
VerticalScrollBarVisibility = ScrollBarVisibility.Disabled,
MaxHeight = 420
};
}
private static UIElement CreateCodeBlock(WorkflowRulesEditorMarkdownCodeBlock codeBlock)
private static RichTextBox CreateSelectableDocument(
IEnumerable<Block> blocks,
Brush? foreground = null,
double fontSize = 14)
{
return new Border()
.Padding(9)
.CornerRadius(6)
.BorderThickness(1)
.BorderBrush(Color.FromRgb(86, 94, 108))
.Background(Color.FromRgb(20, 24, 31))
.Child(new TextBlock()
.Text(codeBlock.Text)
.TextWrapping(TextWrapping.Wrap)
.FontFamily("Consolas")
.Foreground(Color.FromRgb(237, 241, 247)));
var document = new FlowDocument
{
PagePadding = new Thickness(0),
FontFamily = new FontFamily("Segoe UI"),
FontSize = fontSize,
Foreground = foreground ?? PrimaryText
};
foreach (var block in blocks)
{
document.Blocks.Add(block);
}
return new RichTextBox
{
Document = document,
IsReadOnly = true,
IsDocumentEnabled = true,
BorderThickness = new Thickness(0),
Background = Brushes.Transparent,
Foreground = foreground ?? PrimaryText,
Padding = new Thickness(0),
Margin = new Thickness(0),
MinHeight = 0,
HorizontalScrollBarVisibility = ScrollBarVisibility.Disabled,
VerticalScrollBarVisibility = ScrollBarVisibility.Disabled
};
}
private static Paragraph CreateParagraphBlock(double lineHeight = 19)
{
return new Paragraph
{
Margin = new Thickness(0),
LineHeight = lineHeight
};
}
private static Paragraph CreateInlineParagraph(
IReadOnlyList<WorkflowRulesEditorMarkdownInline> inlines,
WorkflowRulesEditorMarkdownLinkResolver linkResolver,
double lineHeight = 19)
{
var paragraph = CreateParagraphBlock(lineHeight);
foreach (var inline in inlines)
{
paragraph.Inlines.Add(CreateInline(inline, linkResolver));
}
return paragraph;
}
private static Inline CreateInline(
WorkflowRulesEditorMarkdownInline inline,
WorkflowRulesEditorMarkdownLinkResolver linkResolver)
{
if (inline.Style == WorkflowRulesEditorMarkdownInlineStyle.Link)
{
return CreateLink(inline, linkResolver);
}
if (inline.Style == WorkflowRulesEditorMarkdownInlineStyle.Image)
{
return CreateImage(inline, linkResolver);
}
if (inline.Style == WorkflowRulesEditorMarkdownInlineStyle.Code)
{
return CreateInlineCode(inline.Text);
}
var run = new Run(inline.Text);
switch (inline.Style)
{
case WorkflowRulesEditorMarkdownInlineStyle.Bold:
run.FontWeight = FontWeights.SemiBold;
break;
case WorkflowRulesEditorMarkdownInlineStyle.BoldItalic:
run.FontWeight = FontWeights.SemiBold;
run.FontStyle = FontStyles.Italic;
break;
case WorkflowRulesEditorMarkdownInlineStyle.Italic:
run.FontStyle = FontStyles.Italic;
break;
case WorkflowRulesEditorMarkdownInlineStyle.Strikethrough:
run.TextDecorations = TextDecorations.Strikethrough;
break;
}
return run;
}
private static Inline CreateLink(
WorkflowRulesEditorMarkdownInline inline,
WorkflowRulesEditorMarkdownLinkResolver linkResolver)
{
var hyperlink = new Hyperlink(new Run(inline.Text))
{
Foreground = LinkText,
TextDecorations = TextDecorations.Underline,
ToolTip = inline.LinkTarget,
Cursor = Cursors.Hand
};
if (!string.IsNullOrWhiteSpace(inline.LinkTarget))
{
hyperlink.Click += (_, args) =>
{
OpenTarget(inline.LinkTarget, linkResolver);
args.Handled = true;
};
}
return hyperlink;
}
private static Inline CreateInlineCode(string text)
{
var code = new TextBlock
{
Text = text,
FontFamily = new FontFamily("Consolas"),
FontSize = 13,
Foreground = CodeText,
Background = CodeBackground,
Padding = new Thickness(0),
Margin = new Thickness(0),
LineHeight = 16,
VerticalAlignment = VerticalAlignment.Center
};
return new InlineUIContainer(new Border
{
BorderThickness = new Thickness(1),
BorderBrush = CodeBorder,
CornerRadius = new CornerRadius(4),
Background = CodeBackground,
Padding = new Thickness(4, 1, 4, 1),
Margin = new Thickness(1, 0, 1, 0),
Child = code
})
{
BaselineAlignment = BaselineAlignment.Center
};
}
private static Inline CreateImage(
WorkflowRulesEditorMarkdownInline inline,
WorkflowRulesEditorMarkdownLinkResolver linkResolver)
{
var preview = CreateImagePreview(inline.Text, inline.LinkTarget, linkResolver);
return new InlineUIContainer(preview)
{
BaselineAlignment = BaselineAlignment.Center
};
}
private static UIElement CreateImagePreview(
string altText,
string? target,
WorkflowRulesEditorMarkdownLinkResolver linkResolver)
{
var border = new Border
{
Width = 180,
Height = 112,
Margin = new Thickness(0, 4, 0, 4),
Padding = new Thickness(6),
BorderThickness = new Thickness(1),
BorderBrush = CodeBorder,
CornerRadius = new CornerRadius(6),
Background = ImagePlaceholderBackground,
ToolTip = target,
Cursor = Cursors.Hand
};
border.AddHandler(UIElement.MouseLeftButtonUpEvent, new MouseButtonEventHandler((_, args) =>
{
OpenTarget(target, linkResolver);
args.Handled = true;
}), handledEventsToo: true);
if (TryCreateBitmap(target, linkResolver, out var bitmap))
{
var image = new Image
{
Source = bitmap,
Stretch = Stretch.Uniform,
ToolTip = target
};
image.ImageFailed += (_, _) => border.Child = CreateImagePlaceholder(altText);
border.Child = image;
}
else
{
border.Child = CreateImagePlaceholder(altText);
}
return border;
}
private static UIElement CreateImagePlaceholder(string altText)
{
return new TextBlock
{
Text = string.IsNullOrWhiteSpace(altText) ? "Image" : altText,
Foreground = PrimaryText,
TextAlignment = TextAlignment.Center,
TextWrapping = TextWrapping.Wrap,
VerticalAlignment = VerticalAlignment.Center,
HorizontalAlignment = HorizontalAlignment.Center
};
}
private static bool TryCreateBitmap(
string? target,
WorkflowRulesEditorMarkdownLinkResolver linkResolver,
out BitmapImage bitmap)
{
bitmap = null!;
if (string.IsNullOrWhiteSpace(target) || !linkResolver.TryResolveImageUri(target, out var uri))
{
return false;
}
try
{
bitmap = new BitmapImage();
bitmap.BeginInit();
bitmap.CacheOption = BitmapCacheOption.OnLoad;
bitmap.UriSource = uri;
bitmap.DecodePixelWidth = 360;
bitmap.EndInit();
bitmap.Freeze();
return true;
}
catch
{
return false;
}
}
private static void OpenTarget(
string? target,
WorkflowRulesEditorMarkdownLinkResolver linkResolver)
{
if (string.IsNullOrWhiteSpace(target))
{
return;
}
try
{
var openTarget = linkResolver.ResolveOpenTarget(target);
if (string.IsNullOrWhiteSpace(openTarget))
{
return;
}
Process.Start(new ProcessStartInfo(openTarget)
{
UseShellExecute = true
});
}
catch
{
}
}
private static void AddTableCell(
Grid grid,
int row,
int column,
string text,
Brush background,
FontWeight fontWeight)
{
var paragraph = CreateParagraphBlock();
paragraph.Inlines.Add(new Run(text) { FontWeight = fontWeight });
var document = CreateSelectableDocument([paragraph], PrimaryText);
document.HorizontalScrollBarVisibility = ScrollBarVisibility.Hidden;
var border = new Border
{
Padding = new Thickness(8, 4, 8, 4),
BorderThickness = new Thickness(0, 0, 1, 1),
BorderBrush = CodeBorder,
Background = background,
Child = document
};
Grid.SetRow(border, row);
Grid.SetColumn(border, column);
grid.Children.Add(border);
}
private static IReadOnlyList<string> PadCells(IReadOnlyList<string> cells, int count)