Added the Batch Processing Assistant
The assistant processes all documents of a folder in one batch run. Each
document is extracted to Markdown by the Rust runtime and sent to the
selected provider together with the user's instructions.
The instructions come from one of three sources: a free prompt, one of
the existing document analysis policies including its minimum provider
confidence, or a file the user imports.
The output is either one Markdown file per document, or one CSV results
table in which each answer becomes a row. The user can name the results
table; its columns are the document and the answer.
Every run writes a log named log.csv with the document, time, model,
status, and the reason for any error. When a later run finds a log in
the output folder, the assistant asks whether to continue it. Continuing
processes only the documents that failed or whose results no longer
exist, which recovers runs interrupted by a crash or by documents
exceeding the context window of the model. A single failing document
never stops the run, and the run can be canceled at any time.
Columns are separated by a vertical bar and quoted per RFC 4180, so that
the files open in spreadsheet applications regardless of the list
separator of the user. Documents are identified by their path relative
to the input folder, because two subfolders may contain a document of
the same name.
Includes the English and German localization.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-09 16:05:18 +00:00
@attribute [Route(Routes.ASSISTANT_BATCH_PROCESSING)]
2026-08-11 10:14:22 +00:00
@inherits AssistantBaseCore<AIStudio.Dialogs.Settings.SettingsDialogBatchProcessing>
Added the Batch Processing Assistant
The assistant processes all documents of a folder in one batch run. Each
document is extracted to Markdown by the Rust runtime and sent to the
selected provider together with the user's instructions.
The instructions come from one of three sources: a free prompt, one of
the existing document analysis policies including its minimum provider
confidence, or a file the user imports.
The output is either one Markdown file per document, or one CSV results
table in which each answer becomes a row. The user can name the results
table; its columns are the document and the answer.
Every run writes a log named log.csv with the document, time, model,
status, and the reason for any error. When a later run finds a log in
the output folder, the assistant asks whether to continue it. Continuing
processes only the documents that failed or whose results no longer
exist, which recovers runs interrupted by a crash or by documents
exceeding the context window of the model. A single failing document
never stops the run, and the run can be canceled at any time.
Columns are separated by a vertical bar and quoted per RFC 4180, so that
the files open in spreadsheet applications regardless of the list
separator of the user. Documents are identified by their path relative
to the input folder, because two subfolders may contain a document of
the same name.
Includes the English and German localization.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-09 16:05:18 +00:00
@using AIStudio.Settings.DataModel
2026-08-11 10:58:58 +00:00
@using AIStudio.Tools.Rust
Added the Batch Processing Assistant
The assistant processes all documents of a folder in one batch run. Each
document is extracted to Markdown by the Rust runtime and sent to the
selected provider together with the user's instructions.
The instructions come from one of three sources: a free prompt, one of
the existing document analysis policies including its minimum provider
confidence, or a file the user imports.
The output is either one Markdown file per document, or one CSV results
table in which each answer becomes a row. The user can name the results
table; its columns are the document and the answer.
Every run writes a log named log.csv with the document, time, model,
status, and the reason for any error. When a later run finds a log in
the output folder, the assistant asks whether to continue it. Continuing
processes only the documents that failed or whose results no longer
exist, which recovers runs interrupted by a crash or by documents
exceeding the context window of the model. A single failing document
never stops the run, and the run can be canceled at any time.
Columns are separated by a vertical bar and quoted per RFC 4180, so that
the files open in spreadsheet applications regardless of the list
separator of the user. Documents are identified by their path relative
to the input folder, because two subfolders may contain a document of
the same name.
Includes the English and German localization.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-09 16:05:18 +00:00
<MudText Typo="Typo.h5" Class="mb-3">
@T("Input")
</MudText>
<SelectDirectory Label="@T("Folder containing your documents")" DirectoryDialogTitle="@T("Select the folder containing your documents")" @bind-Directory="@this.inputDirectory" Validation="@this.ValidateInputDirectory" Disabled="@this.isProcessingBatch"/>
2026-08-11 11:27:44 +00:00
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="2" Class="mb-1">
<MudTextField T="string" @bind-Text="@this.filePatterns" Validation="@this.ValidateFilePatterns" Immediate="@true" Disabled="@this.isProcessingBatch" Label="@T("File patterns")" HelperText="@T("Which files should be processed? Separate multiple patterns with a semicolon, e.g., *.pdf;*.docx")" AdornmentIcon="@Icons.Material.Filled.FilterAlt" Adornment="Adornment.Start" Variant="Variant.Outlined" Margin="Margin.Normal" Class="flex-grow-1" UserAttributes="@USER_INPUT_ATTRIBUTES"/>
<MudButton Variant="Variant.Outlined" StartIcon="@Icons.Material.Filled.Restore" Disabled="@this.isProcessingBatch" OnClick="@this.RestoreDefaultFilePatterns">
@T("Restore default patterns")
</MudButton>
</MudStack>
Added the Batch Processing Assistant
The assistant processes all documents of a folder in one batch run. Each
document is extracted to Markdown by the Rust runtime and sent to the
selected provider together with the user's instructions.
The instructions come from one of three sources: a free prompt, one of
the existing document analysis policies including its minimum provider
confidence, or a file the user imports.
The output is either one Markdown file per document, or one CSV results
table in which each answer becomes a row. The user can name the results
table; its columns are the document and the answer.
Every run writes a log named log.csv with the document, time, model,
status, and the reason for any error. When a later run finds a log in
the output folder, the assistant asks whether to continue it. Continuing
processes only the documents that failed or whose results no longer
exist, which recovers runs interrupted by a crash or by documents
exceeding the context window of the model. A single failing document
never stops the run, and the run can be canceled at any time.
Columns are separated by a vertical bar and quoted per RFC 4180, so that
the files open in spreadsheet applications regardless of the list
separator of the user. Documents are identified by their path relative
to the input folder, because two subfolders may contain a document of
the same name.
Includes the English and German localization.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-09 16:05:18 +00:00
2026-08-11 12:33:03 +00:00
<MudJustifiedText Typo="Typo.body2" Class="mb-3">
@T("Supported audio and video files are transcribed automatically without an additional dialog. Each transcript is stored next to its media file as '<media-file>.transcript.md' and reused when an interrupted run is continued.")
</MudJustifiedText>
Added the Batch Processing Assistant
The assistant processes all documents of a folder in one batch run. Each
document is extracted to Markdown by the Rust runtime and sent to the
selected provider together with the user's instructions.
The instructions come from one of three sources: a free prompt, one of
the existing document analysis policies including its minimum provider
confidence, or a file the user imports.
The output is either one Markdown file per document, or one CSV results
table in which each answer becomes a row. The user can name the results
table; its columns are the document and the answer.
Every run writes a log named log.csv with the document, time, model,
status, and the reason for any error. When a later run finds a log in
the output folder, the assistant asks whether to continue it. Continuing
processes only the documents that failed or whose results no longer
exist, which recovers runs interrupted by a crash or by documents
exceeding the context window of the model. A single failing document
never stops the run, and the run can be canceled at any time.
Columns are separated by a vertical bar and quoted per RFC 4180, so that
the files open in spreadsheet applications regardless of the list
separator of the user. Documents are identified by their path relative
to the input folder, because two subfolders may contain a document of
the same name.
Includes the English and German localization.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-09 16:05:18 +00:00
<MudTextSwitch Label="@T("Include subfolders?")" Disabled="@this.isProcessingBatch" Value="@this.includeSubdirectories" ValueChanged="@(v => this.includeSubdirectories = v)" LabelOn="@T("Yes, process files in subfolders as well")" LabelOff="@T("No, only process files in the selected folder")"/>
2026-08-11 10:58:58 +00:00
@if (this.includeSubdirectories)
{
<MudJustifiedText Typo="Typo.body2" Class="mb-3">
@T("A separate output folder is excluded from document discovery. This includes the default 'ai-results' folder, so results from an earlier run are not processed again. If the input folder itself is used for output, known batch result files are excluded instead.")
</MudJustifiedText>
}
Added the Batch Processing Assistant
The assistant processes all documents of a folder in one batch run. Each
document is extracted to Markdown by the Rust runtime and sent to the
selected provider together with the user's instructions.
The instructions come from one of three sources: a free prompt, one of
the existing document analysis policies including its minimum provider
confidence, or a file the user imports.
The output is either one Markdown file per document, or one CSV results
table in which each answer becomes a row. The user can name the results
table; its columns are the document and the answer.
Every run writes a log named log.csv with the document, time, model,
status, and the reason for any error. When a later run finds a log in
the output folder, the assistant asks whether to continue it. Continuing
processes only the documents that failed or whose results no longer
exist, which recovers runs interrupted by a crash or by documents
exceeding the context window of the model. A single failing document
never stops the run, and the run can be canceled at any time.
Columns are separated by a vertical bar and quoted per RFC 4180, so that
the files open in spreadsheet applications regardless of the list
separator of the user. Documents are identified by their path relative
to the input folder, because two subfolders may contain a document of
the same name.
Includes the English and German localization.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-09 16:05:18 +00:00
<MudText Typo="Typo.h5" Class="mb-3 mt-6">
@T("Instructions")
</MudText>
2026-08-11 10:14:22 +00:00
<MudSelect T="BatchProcessingPromptSource" Value="@this.promptSource" ValueChanged="@this.PromptSourceChanged" Disabled="@this.isProcessingBatch" AdornmentIcon="@Icons.Material.Filled.EditNote" Adornment="Adornment.Start" Label="@T("Source of the instructions")" Variant="Variant.Outlined" Margin="Margin.Dense" Class="mb-3">
Added the Batch Processing Assistant
The assistant processes all documents of a folder in one batch run. Each
document is extracted to Markdown by the Rust runtime and sent to the
selected provider together with the user's instructions.
The instructions come from one of three sources: a free prompt, one of
the existing document analysis policies including its minimum provider
confidence, or a file the user imports.
The output is either one Markdown file per document, or one CSV results
table in which each answer becomes a row. The user can name the results
table; its columns are the document and the answer.
Every run writes a log named log.csv with the document, time, model,
status, and the reason for any error. When a later run finds a log in
the output folder, the assistant asks whether to continue it. Continuing
processes only the documents that failed or whose results no longer
exist, which recovers runs interrupted by a crash or by documents
exceeding the context window of the model. A single failing document
never stops the run, and the run can be canceled at any time.
Columns are separated by a vertical bar and quoted per RFC 4180, so that
the files open in spreadsheet applications regardless of the list
separator of the user. Documents are identified by their path relative
to the input folder, because two subfolders may contain a document of
the same name.
Includes the English and German localization.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-09 16:05:18 +00:00
@foreach (var source in Enum.GetValues<BatchProcessingPromptSource>())
{
<MudSelectItem Value="@source">
@source.Name()
</MudSelectItem>
}
</MudSelect>
@if (this.promptSource is BatchProcessingPromptSource.FREE_PROMPT)
{
2026-08-11 11:23:52 +00:00
<ReadFileContent Text="@T("Load prompt from file")" @bind-FileContent="@this.freePrompt" EnableDragDrop="true" Layer="@DropLayers.ASSISTANTS" CatchAllDocuments="true" Disabled="@this.isProcessingBatch"/>
2026-08-11 10:58:58 +00:00
Added the Batch Processing Assistant
The assistant processes all documents of a folder in one batch run. Each
document is extracted to Markdown by the Rust runtime and sent to the
selected provider together with the user's instructions.
The instructions come from one of three sources: a free prompt, one of
the existing document analysis policies including its minimum provider
confidence, or a file the user imports.
The output is either one Markdown file per document, or one CSV results
table in which each answer becomes a row. The user can name the results
table; its columns are the document and the answer.
Every run writes a log named log.csv with the document, time, model,
status, and the reason for any error. When a later run finds a log in
the output folder, the assistant asks whether to continue it. Continuing
processes only the documents that failed or whose results no longer
exist, which recovers runs interrupted by a crash or by documents
exceeding the context window of the model. A single failing document
never stops the run, and the run can be canceled at any time.
Columns are separated by a vertical bar and quoted per RFC 4180, so that
the files open in spreadsheet applications regardless of the list
separator of the user. Documents are identified by their path relative
to the input folder, because two subfolders may contain a document of
the same name.
Includes the English and German localization.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-09 16:05:18 +00:00
<MudTextField T="string" @bind-Text="@this.freePrompt" Validation="@this.ValidateFreePrompt" Immediate="@true" Disabled="@this.isProcessingBatch" Label="@T("What should the AI do with each document?")" HelperText="@T("These instructions are applied to every single document of the batch run.")" Variant="Variant.Outlined" Margin="Margin.Normal" Lines="5" AutoGrow="@true" MaxLines="26" Class="mb-3" UserAttributes="@USER_INPUT_ATTRIBUTES"/>
}
else if (this.promptSource is BatchProcessingPromptSource.FILE_IMPORT)
{
2026-08-11 11:27:44 +00:00
<ReadFileContent Text="@T("Select the file with your instructions")" @bind-FileContent="@this.ImportedPrompt" Filter="@([FileTypes.MARKDOWN])" ShowAttachedDocumentState="@true" EnableDragDrop="true" Layer="@DropLayers.ASSISTANTS" CatchAllDocuments="true" Disabled="@this.isProcessingBatch"/>
2026-08-11 10:14:22 +00:00
@if (!string.IsNullOrWhiteSpace(this.promptFilePath))
{
<MudText Typo="Typo.body2" Class="mb-3">@(string.Format(T("Configured instructions file: {0}"), this.promptFilePath))</MudText>
}
@if (!string.IsNullOrWhiteSpace(this.promptFileLoadIssue))
{
<MudAlert Severity="Severity.Error" Dense="true" Class="mb-3">@this.promptFileLoadIssue</MudAlert>
}
Added the Batch Processing Assistant
The assistant processes all documents of a folder in one batch run. Each
document is extracted to Markdown by the Rust runtime and sent to the
selected provider together with the user's instructions.
The instructions come from one of three sources: a free prompt, one of
the existing document analysis policies including its minimum provider
confidence, or a file the user imports.
The output is either one Markdown file per document, or one CSV results
table in which each answer becomes a row. The user can name the results
table; its columns are the document and the answer.
Every run writes a log named log.csv with the document, time, model,
status, and the reason for any error. When a later run finds a log in
the output folder, the assistant asks whether to continue it. Continuing
processes only the documents that failed or whose results no longer
exist, which recovers runs interrupted by a crash or by documents
exceeding the context window of the model. A single failing document
never stops the run, and the run can be canceled at any time.
Columns are separated by a vertical bar and quoted per RFC 4180, so that
the files open in spreadsheet applications regardless of the list
separator of the user. Documents are identified by their path relative
to the input folder, because two subfolders may contain a document of
the same name.
Includes the English and German localization.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-09 16:05:18 +00:00
<MudJustifiedText Typo="Typo.body2" Class="mb-3">
@T("The content of the selected file is used as the instructions for every single document of the batch run.")
</MudJustifiedText>
}
else
{
2026-08-11 10:14:22 +00:00
@if (this.ConfiguredPolicyIsMissing)
{
<MudAlert Severity="Severity.Warning" Dense="true" Class="mb-3">@T("The configured default policy no longer exists. Please select another document analysis policy.")</MudAlert>
}
Added the Batch Processing Assistant
The assistant processes all documents of a folder in one batch run. Each
document is extracted to Markdown by the Rust runtime and sent to the
selected provider together with the user's instructions.
The instructions come from one of three sources: a free prompt, one of
the existing document analysis policies including its minimum provider
confidence, or a file the user imports.
The output is either one Markdown file per document, or one CSV results
table in which each answer becomes a row. The user can name the results
table; its columns are the document and the answer.
Every run writes a log named log.csv with the document, time, model,
status, and the reason for any error. When a later run finds a log in
the output folder, the assistant asks whether to continue it. Continuing
processes only the documents that failed or whose results no longer
exist, which recovers runs interrupted by a crash or by documents
exceeding the context window of the model. A single failing document
never stops the run, and the run can be canceled at any time.
Columns are separated by a vertical bar and quoted per RFC 4180, so that
the files open in spreadsheet applications regardless of the list
separator of the user. Documents are identified by their path relative
to the input folder, because two subfolders may contain a document of
the same name.
Includes the English and German localization.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-09 16:05:18 +00:00
@if (this.SettingsManager.ConfigurationData.DocumentAnalysis.Policies.Count is 0)
{
<MudJustifiedText Typo="Typo.body1" Class="mb-3">
@T("You have not yet created any document analysis policies. Please use the Document Analysis Assistant to create a policy first.")
</MudJustifiedText>
<MudButton Href="@Routes.ASSISTANT_DOCUMENT_ANALYSIS" Variant="Variant.Filled" Color="Color.Primary" Class="mb-3">
@T("Open the Document Analysis Assistant")
</MudButton>
}
else
{
2026-08-11 10:14:22 +00:00
<MudSelect T="DataDocumentAnalysisPolicy" Value="@this.selectedPolicy" ValueChanged="@this.SelectedPolicyChanged" Disabled="@this.isProcessingBatch" AdornmentIcon="@Icons.Material.Filled.Policy" Adornment="Adornment.Start" Label="@T("Document analysis policy")" Variant="Variant.Outlined" Margin="Margin.Dense" Class="mb-3">
Added the Batch Processing Assistant
The assistant processes all documents of a folder in one batch run. Each
document is extracted to Markdown by the Rust runtime and sent to the
selected provider together with the user's instructions.
The instructions come from one of three sources: a free prompt, one of
the existing document analysis policies including its minimum provider
confidence, or a file the user imports.
The output is either one Markdown file per document, or one CSV results
table in which each answer becomes a row. The user can name the results
table; its columns are the document and the answer.
Every run writes a log named log.csv with the document, time, model,
status, and the reason for any error. When a later run finds a log in
the output folder, the assistant asks whether to continue it. Continuing
processes only the documents that failed or whose results no longer
exist, which recovers runs interrupted by a crash or by documents
exceeding the context window of the model. A single failing document
never stops the run, and the run can be canceled at any time.
Columns are separated by a vertical bar and quoted per RFC 4180, so that
the files open in spreadsheet applications regardless of the list
separator of the user. Documents are identified by their path relative
to the input folder, because two subfolders may contain a document of
the same name.
Includes the English and German localization.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-09 16:05:18 +00:00
@foreach (var policy in this.SettingsManager.ConfigurationData.DocumentAnalysis.Policies)
{
<MudSelectItem Value="@policy">
@policy.PolicyName
</MudSelectItem>
}
</MudSelect>
@if (this.selectedPolicy is not null && !string.IsNullOrWhiteSpace(this.selectedPolicy.PolicyDescription))
{
<MudJustifiedText Typo="Typo.body2" Class="mb-3">
@this.selectedPolicy.PolicyDescription
</MudJustifiedText>
}
}
}
<MudText Typo="Typo.h5" Class="mb-3 mt-6">
@T("Output")
</MudText>
<MudSelect T="BatchProcessingOutputMode" @bind-Value="@this.outputMode" Disabled="@this.isProcessingBatch" AdornmentIcon="@Icons.Material.Filled.Output" Adornment="Adornment.Start" Label="@T("Output mode")" Variant="Variant.Outlined" Margin="Margin.Dense" Class="mb-3">
@foreach (var mode in Enum.GetValues<BatchProcessingOutputMode>())
{
<MudSelectItem Value="@mode">
@mode.Name()
</MudSelectItem>
}
</MudSelect>
@if (this.outputMode is BatchProcessingOutputMode.MARKDOWN_FILES)
{
<MudJustifiedText Typo="Typo.body2" Class="mb-3">
@T("Each answer is stored as its own Markdown file. Those files are named after the document, e.g., the answer for report.pdf is stored as report_result.md.")
</MudJustifiedText>
}
else
{
<MudTextField T="string" @bind-Text="@this.csvFileName" Validation="@this.ValidateCsvFileName" Immediate="@true" Disabled="@this.isProcessingBatch" Label="@T("Name of the results table (optional)")" HelperText="@T("The file name of the CSV results table. We add the ending .csv when it is missing. When left empty, we use 'batch-results.csv'.")" AdornmentIcon="@Icons.Material.Filled.Description" Adornment="Adornment.Start" Variant="Variant.Outlined" Margin="Margin.Normal" Class="mb-3" UserAttributes="@USER_INPUT_ATTRIBUTES"/>
<MudTextField T="string" @bind-Text="@this.resultColumnHeader" Disabled="@this.isProcessingBatch" Label="@T("Header of the result column (optional)")" HelperText="@T("The results table contains one row per document, starting with the file name. Here you can name the column that holds the AI answer, e.g., Summary. When left empty, we use 'Result'.")" AdornmentIcon="@Icons.Material.Filled.TableChart" Adornment="Adornment.Start" Variant="Variant.Outlined" Margin="Margin.Normal" Class="mb-3" UserAttributes="@USER_INPUT_ATTRIBUTES"/>
}
<SelectDirectory Label="@T("Output folder (optional)")" DirectoryDialogTitle="@T("Select the output folder")" @bind-Directory="@this.outputDirectory" Disabled="@this.isProcessingBatch"/>
<MudJustifiedText Typo="Typo.body2" Class="mb-3">
@T("We always write a log named log.csv, which lists every document with its processing time, the model, the status, and the details of any error. Its columns are separated by a vertical bar, so you can open it with any spreadsheet application. When you start another run on the same output folder, we ask you whether to continue that run: documents which failed or are missing in the log are then processed again. When no output folder is selected, everything is written to the subfolder 'ai-results' within the input folder.")
</MudJustifiedText>
<ProviderSelection @bind-ProviderSettings="@this.ProviderSettings" ValidateProvider="@this.ValidatingProviderWithBatchState" Disabled="@this.isProcessingBatch" ExplicitMinimumConfidence="@this.GetMinimumConfidenceLevel()"/>
@if (this.fileResults.Count > 0)
{
<MudText Typo="Typo.h5" Class="mb-3 mt-6">
@T("Progress")
</MudText>
<MudProgressLinear Color="Color.Primary" Value="@(this.fileResults.Count == 0 ? 0 : 100.0 * this.numProcessedFiles / this.fileResults.Count)" Class="mb-1"/>
<MudText Typo="Typo.body2" Class="mb-3">
@(string.Format(T("{0} of {1} files processed"), this.numProcessedFiles, this.fileResults.Count))
</MudText>
@if (this.isProcessingBatch)
{
<MudButton OnClick="@this.CancelBatchProcessingAsync" Variant="Variant.Filled" Color="Color.Error" StartIcon="@Icons.Material.Filled.Cancel" Class="mb-3">
@T("Cancel the batch run")
</MudButton>
}
<MudSimpleTable Dense="@true" Hover="@true" Class="mb-3">
<thead>
<tr>
<th>@T("Status")</th>
<th>@T("File")</th>
<th>@T("Details")</th>
</tr>
</thead>
<tbody>
@foreach (var fileResult in this.fileResults)
{
<tr>
<td>
@switch (fileResult.Status)
{
case BatchProcessingFileStatus.QUEUED:
<MudIcon Icon="@Icons.Material.Filled.Schedule" Size="Size.Small" Title="@T("Queued")"/>
break;
case BatchProcessingFileStatus.PROCESSING:
<MudProgressCircular Color="Color.Primary" Size="Size.Small" Indeterminate="@true"/>
break;
case BatchProcessingFileStatus.DONE:
<MudIcon Icon="@Icons.Material.Filled.CheckCircle" Color="Color.Success" Size="Size.Small" Title="@T("Done")"/>
break;
case BatchProcessingFileStatus.FAILED:
<MudIcon Icon="@Icons.Material.Filled.Error" Color="Color.Error" Size="Size.Small" Title="@T("Failed")"/>
break;
case BatchProcessingFileStatus.CANCELED:
<MudIcon Icon="@Icons.Material.Filled.Cancel" Color="Color.Warning" Size="Size.Small" Title="@T("Canceled")"/>
break;
}
</td>
<td>@fileResult.RelativePath</td>
<td>@fileResult.Message</td>
</tr>
}
</tbody>
</MudSimpleTable>
2026-08-11 11:27:44 +00:00
}