mirror of
https://github.com/MindWorkAI/AI-Studio.git
synced 2026-08-12 11:32:11 +00:00
Make batch CSV separators configurable
This commit is contained in:
parent
e9e394ed96
commit
9e07155e3d
@ -126,12 +126,26 @@ 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"/>
|
||||
|
||||
<MudSelect T="BatchProcessingCsvSeparator" @bind-Value="@this.csvSeparator" Disabled="@this.isProcessingBatch" AdornmentIcon="@Icons.Material.Filled.FormatListBulleted" Adornment="Adornment.Start" Label="@T("Column separator")" HelperText="@T("Choose which character separates the columns of the results table.")" Variant="Variant.Outlined" Margin="Margin.Dense" Class="mb-3">
|
||||
@foreach (var separator in Enum.GetValues<BatchProcessingCsvSeparator>())
|
||||
{
|
||||
<MudSelectItem Value="@separator">
|
||||
@separator.Name()
|
||||
</MudSelectItem>
|
||||
}
|
||||
</MudSelect>
|
||||
|
||||
@if (this.csvSeparator is BatchProcessingCsvSeparator.CUSTOM)
|
||||
{
|
||||
<MudTextField T="string" @bind-Text="@this.customCsvSeparator" Validation="@this.ValidateCustomCsvSeparator" Immediate="@true" Disabled="@this.isProcessingBatch" Label="@T("Custom column separator")" HelperText="@T("Enter one punctuation or symbol character.")" AdornmentIcon="@Icons.Material.Filled.Edit" 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.")
|
||||
@T("We always write a semicolon-separated log named log.csv, which lists every document with its processing time, the model, the status, and the details of any error. 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()"/>
|
||||
|
||||
@ -106,9 +106,9 @@ public partial class AssistantBatchProcessing
|
||||
private async Task WriteLogAsync(string resolvedOutputDirectory)
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
sb.AppendLine(BatchProcessingCsv.ToCsvRow(T("File"), T("Time"), T("Model"), T("Status"), T("Details")));
|
||||
sb.AppendLine(BatchProcessingCsv.ToCsvRow(LOG_SEPARATOR, T("File"), T("Time"), T("Model"), T("Status"), T("Details")));
|
||||
foreach (var fileResult in this.fileResults.Where(x => x.Status is not BatchProcessingFileStatus.QUEUED and not BatchProcessingFileStatus.PROCESSING))
|
||||
sb.AppendLine(BatchProcessingCsv.ToCsvRow(fileResult.RelativePath, fileResult.ProcessedAt.ToString(TIME_FORMAT, CultureInfo.InvariantCulture), fileResult.ModelName, fileResult.Status.ToString(), fileResult.Message));
|
||||
sb.AppendLine(BatchProcessingCsv.ToCsvRow(LOG_SEPARATOR, fileResult.RelativePath, fileResult.ProcessedAt.ToString(TIME_FORMAT, CultureInfo.InvariantCulture), fileResult.ModelName, fileResult.Status.ToString(), fileResult.Message));
|
||||
|
||||
await this.WriteCsvFileAsync(Path.Join(resolvedOutputDirectory, LOG_FILENAME), sb.ToString());
|
||||
}
|
||||
@ -118,10 +118,11 @@ public partial class AssistantBatchProcessing
|
||||
/// </summary>
|
||||
private async Task WriteResultsTableAsync(string resolvedOutputDirectory)
|
||||
{
|
||||
var separator = this.csvSeparator.Character(this.customCsvSeparator);
|
||||
var sb = new StringBuilder();
|
||||
sb.AppendLine(BatchProcessingCsv.ToCsvRow(T("File"), this.ResultColumnHeader));
|
||||
sb.AppendLine(BatchProcessingCsv.ToCsvRow(separator, T("File"), this.ResultColumnHeader));
|
||||
foreach (var fileResult in this.fileResults.Where(x => x.Status is BatchProcessingFileStatus.DONE))
|
||||
sb.AppendLine(BatchProcessingCsv.ToCsvRow(fileResult.RelativePath, fileResult.ResultText));
|
||||
sb.AppendLine(BatchProcessingCsv.ToCsvRow(separator, fileResult.RelativePath, fileResult.ResultText));
|
||||
|
||||
await this.WriteCsvFileAsync(Path.Join(resolvedOutputDirectory, this.ResolveResultsFileName()), sb.ToString());
|
||||
}
|
||||
@ -175,7 +176,7 @@ public partial class AssistantBatchProcessing
|
||||
try
|
||||
{
|
||||
var content = await File.ReadAllTextAsync(logFilePath);
|
||||
var rows = BatchProcessingCsv.Parse(content);
|
||||
var rows = BatchProcessingCsv.ParseWithDetectedSeparator(content, 5, LOG_SEPARATOR, '|');
|
||||
|
||||
// The first row is the header, which we skip:
|
||||
foreach (var row in rows.Skip(1))
|
||||
@ -211,7 +212,9 @@ public partial class AssistantBatchProcessing
|
||||
return results;
|
||||
|
||||
var content = await File.ReadAllTextAsync(resultsFilePath);
|
||||
foreach (var row in BatchProcessingCsv.Parse(content).Skip(1))
|
||||
var configuredSeparator = this.csvSeparator.Character(this.customCsvSeparator);
|
||||
var rows = BatchProcessingCsv.ParseWithDetectedSeparator(content, 2, configuredSeparator, ';', '|', ',', '\t');
|
||||
foreach (var row in rows.Skip(1))
|
||||
{
|
||||
if (row.Count < 2 || string.IsNullOrWhiteSpace(row[0]))
|
||||
continue;
|
||||
|
||||
@ -18,6 +18,8 @@ public partial class AssistantBatchProcessing
|
||||
private static readonly AssistantSessionStateKey<BatchProcessingOutputMode> OUTPUT_MODE_STATE_KEY = new(nameof(outputMode));
|
||||
private static readonly AssistantSessionStateKey<string> RESULT_COLUMN_HEADER_STATE_KEY = new(nameof(resultColumnHeader));
|
||||
private static readonly AssistantSessionStateKey<string> CSV_FILE_NAME_STATE_KEY = new(nameof(csvFileName));
|
||||
private static readonly AssistantSessionStateKey<BatchProcessingCsvSeparator> CSV_SEPARATOR_STATE_KEY = new(nameof(csvSeparator));
|
||||
private static readonly AssistantSessionStateKey<string> CUSTOM_CSV_SEPARATOR_STATE_KEY = new(nameof(customCsvSeparator));
|
||||
private static readonly AssistantSessionStateKey<List<BatchProcessingFileResult>> FILE_RESULTS_STATE_KEY = new(nameof(fileResults));
|
||||
private static readonly AssistantSessionStateKey<HashSet<string>> USED_RESULT_FILE_NAMES_STATE_KEY = new(nameof(usedResultFileNames));
|
||||
private static readonly AssistantSessionStateKey<bool> IS_PROCESSING_BATCH_STATE_KEY = new(nameof(isProcessingBatch));
|
||||
@ -40,6 +42,8 @@ public partial class AssistantBatchProcessing
|
||||
state.Set(OUTPUT_MODE_STATE_KEY, this.outputMode);
|
||||
state.Set(RESULT_COLUMN_HEADER_STATE_KEY, this.resultColumnHeader);
|
||||
state.Set(CSV_FILE_NAME_STATE_KEY, this.csvFileName);
|
||||
state.Set(CSV_SEPARATOR_STATE_KEY, this.csvSeparator);
|
||||
state.Set(CUSTOM_CSV_SEPARATOR_STATE_KEY, this.customCsvSeparator);
|
||||
state.SetList(FILE_RESULTS_STATE_KEY, this.fileResults.Select(CloneFileResult));
|
||||
state.SetHashSet(USED_RESULT_FILE_NAMES_STATE_KEY, this.usedResultFileNames);
|
||||
state.Set(IS_PROCESSING_BATCH_STATE_KEY, this.isProcessingBatch);
|
||||
@ -63,6 +67,8 @@ public partial class AssistantBatchProcessing
|
||||
state.Restore(OUTPUT_MODE_STATE_KEY, value => this.outputMode = value);
|
||||
state.Restore(RESULT_COLUMN_HEADER_STATE_KEY, value => this.resultColumnHeader = value);
|
||||
state.Restore(CSV_FILE_NAME_STATE_KEY, value => this.csvFileName = value);
|
||||
state.Restore(CSV_SEPARATOR_STATE_KEY, value => this.csvSeparator = value);
|
||||
state.Restore(CUSTOM_CSV_SEPARATOR_STATE_KEY, value => this.customCsvSeparator = value);
|
||||
state.Restore(FILE_RESULTS_STATE_KEY, values =>
|
||||
{
|
||||
this.fileResults.Clear();
|
||||
|
||||
@ -56,6 +56,18 @@ public partial class AssistantBatchProcessing
|
||||
return null;
|
||||
}
|
||||
|
||||
private string? ValidateCustomCsvSeparator(string separator)
|
||||
{
|
||||
if (this.outputMode is not BatchProcessingOutputMode.TABLE_ONLY
|
||||
|| this.csvSeparator is not BatchProcessingCsvSeparator.CUSTOM)
|
||||
return null;
|
||||
|
||||
if (!BatchProcessingCsvSeparatorExtensions.IsValidCustomSeparator(separator))
|
||||
return T("Please enter exactly one punctuation or symbol character. Letters, numbers, spaces, quotation marks, and line breaks cannot be used as CSV separators.");
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private string? ValidateFreePrompt(string prompt)
|
||||
{
|
||||
if (this.promptSource is BatchProcessingPromptSource.FREE_PROMPT && string.IsNullOrWhiteSpace(prompt))
|
||||
|
||||
@ -17,6 +17,7 @@ public partial class AssistantBatchProcessing : AssistantBaseCore<SettingsDialog
|
||||
private const string RESULT_FILE_SUFFIX = "_result.md";
|
||||
private const string TRANSCRIPT_FILE_SUFFIX = ".transcript.md";
|
||||
private const string TIME_FORMAT = "yyyy-MM-dd HH:mm:ss";
|
||||
private const char LOG_SEPARATOR = ';';
|
||||
|
||||
/// <summary>
|
||||
/// The name of the log file. It is fixed, so that a later batch run finds
|
||||
@ -88,6 +89,8 @@ public partial class AssistantBatchProcessing : AssistantBaseCore<SettingsDialog
|
||||
private BatchProcessingOutputMode outputMode = BatchProcessingOutputMode.MARKDOWN_FILES;
|
||||
private string resultColumnHeader = string.Empty;
|
||||
private string csvFileName = string.Empty;
|
||||
private BatchProcessingCsvSeparator csvSeparator = BatchProcessingCsvSeparator.SEMICOLON;
|
||||
private string customCsvSeparator = string.Empty;
|
||||
|
||||
private readonly List<BatchProcessingFileResult> fileResults = [];
|
||||
private readonly HashSet<string> usedResultFileNames = new(StringComparer.OrdinalIgnoreCase);
|
||||
@ -156,6 +159,8 @@ public partial class AssistantBatchProcessing : AssistantBaseCore<SettingsDialog
|
||||
this.outputMode = BatchProcessingOutputMode.MARKDOWN_FILES;
|
||||
this.resultColumnHeader = string.Empty;
|
||||
this.csvFileName = string.Empty;
|
||||
this.csvSeparator = BatchProcessingCsvSeparator.SEMICOLON;
|
||||
this.customCsvSeparator = string.Empty;
|
||||
return;
|
||||
}
|
||||
|
||||
@ -171,6 +176,8 @@ public partial class AssistantBatchProcessing : AssistantBaseCore<SettingsDialog
|
||||
this.outputMode = settings.OutputMode;
|
||||
this.resultColumnHeader = settings.ResultColumnHeader;
|
||||
this.csvFileName = settings.CsvFileName;
|
||||
this.csvSeparator = settings.CsvSeparator;
|
||||
this.customCsvSeparator = settings.CustomCsvSeparator;
|
||||
}
|
||||
|
||||
private async Task LoadConfiguredPromptFileAsync()
|
||||
|
||||
@ -4,28 +4,30 @@ namespace AIStudio.Assistants.BatchProcessing;
|
||||
|
||||
/// <summary>
|
||||
/// Reads and writes the CSV files of the batch processing assistant. Fields
|
||||
/// are quoted according to RFC 4180, but the separator is a vertical bar, so
|
||||
/// that the files open nicely in spreadsheet applications regardless of the
|
||||
/// list separator of the user's locale.
|
||||
/// are quoted according to RFC 4180 using the separator selected for the
|
||||
/// respective file.
|
||||
/// </summary>
|
||||
public static class BatchProcessingCsv
|
||||
{
|
||||
private const char SEPARATOR = '|';
|
||||
|
||||
public static string ToCsvRow(params string[] fields) => string.Join(SEPARATOR, fields.Select(ToCsvField));
|
||||
public static string ToCsvRow(char separator, params string[] fields) => string.Join(separator, fields.Select(field => ToCsvField(field, separator)));
|
||||
|
||||
/// <summary>
|
||||
/// Quotes one CSV field according to RFC 4180.
|
||||
/// </summary>
|
||||
private static string ToCsvField(string text)
|
||||
private static string ToCsvField(string text, char separator)
|
||||
{
|
||||
if (string.IsNullOrEmpty(text))
|
||||
return string.Empty;
|
||||
|
||||
if (!text.Contains(SEPARATOR) && !text.Contains('"') && !text.Contains('\n') && !text.Contains('\r'))
|
||||
// Quoting the complete field is important for long and multi-line AI
|
||||
// answers: neither separators nor line breaks within an answer may
|
||||
// create another column or row.
|
||||
if (!text.Contains(separator) && !text.Contains('"') && !text.Contains('\n') && !text.Contains('\r'))
|
||||
return text;
|
||||
|
||||
return $"\"{text.Replace("\"", "\"\"")}\"";
|
||||
return $"""
|
||||
"{text.Replace("\"", "\"\"")}"
|
||||
""";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -35,7 +37,7 @@ public static class BatchProcessingCsv
|
||||
/// We parse the file ourselves instead of splitting lines, because quoted
|
||||
/// fields may contain the separator and line breaks.
|
||||
/// </remarks>
|
||||
public static List<List<string>> Parse(string content)
|
||||
private static List<List<string>> Parse(string content, char separator)
|
||||
{
|
||||
var rows = new List<List<string>>();
|
||||
var fields = new List<string>();
|
||||
@ -73,7 +75,7 @@ public static class BatchProcessingCsv
|
||||
hasContent = true;
|
||||
break;
|
||||
|
||||
case SEPARATOR:
|
||||
case var _ when character == separator:
|
||||
hasContent = true;
|
||||
EndField();
|
||||
break;
|
||||
@ -113,4 +115,74 @@ public static class BatchProcessingCsv
|
||||
hasContent = false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Detects the separator from the first CSV record and parses the complete
|
||||
/// content with it. Preferred separators are used as fallbacks for files
|
||||
/// whose first record does not reveal a valid separator.
|
||||
/// </summary>
|
||||
public static List<List<string>> ParseWithDetectedSeparator(string content, int expectedNumFields, params char[] preferredSeparators)
|
||||
{
|
||||
var firstRecord = ReadFirstRecord(content);
|
||||
var candidates = new List<char>();
|
||||
var isQuoted = false;
|
||||
for (var index = 0; index < firstRecord.Length; index++)
|
||||
{
|
||||
var character = firstRecord[index];
|
||||
if (character is '"')
|
||||
{
|
||||
if (isQuoted && index + 1 < firstRecord.Length && firstRecord[index + 1] is '"')
|
||||
{
|
||||
index++;
|
||||
continue;
|
||||
}
|
||||
|
||||
isQuoted = !isQuoted;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!isQuoted
|
||||
&& character is not '\r' and not '\n'
|
||||
&& (char.IsPunctuation(character) || char.IsSymbol(character) || character is '\t')
|
||||
&& !candidates.Contains(character))
|
||||
candidates.Add(character);
|
||||
}
|
||||
|
||||
foreach (var separator in preferredSeparators)
|
||||
{
|
||||
if (!candidates.Contains(separator))
|
||||
candidates.Add(separator);
|
||||
}
|
||||
|
||||
foreach (var separator in candidates)
|
||||
{
|
||||
var header = Parse(firstRecord, separator);
|
||||
if (header.Count is 1 && header[0].Count == expectedNumFields)
|
||||
return Parse(content, separator);
|
||||
}
|
||||
|
||||
throw new InvalidDataException("Was not able to detect the CSV separator.");
|
||||
}
|
||||
|
||||
private static string ReadFirstRecord(string content)
|
||||
{
|
||||
var isQuoted = false;
|
||||
for (var index = 0; index < content.Length; index++)
|
||||
{
|
||||
if (content[index] is '"')
|
||||
{
|
||||
if (isQuoted && index + 1 < content.Length && content[index + 1] is '"')
|
||||
{
|
||||
index++;
|
||||
continue;
|
||||
}
|
||||
|
||||
isQuoted = !isQuoted;
|
||||
}
|
||||
else if (content[index] is '\n' && !isQuoted)
|
||||
return content[..(index + 1)];
|
||||
}
|
||||
|
||||
return content;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,13 @@
|
||||
namespace AIStudio.Assistants.BatchProcessing;
|
||||
|
||||
/// <summary>
|
||||
/// Defines the separators available for Batch Processing result tables.
|
||||
/// </summary>
|
||||
public enum BatchProcessingCsvSeparator
|
||||
{
|
||||
COMMA,
|
||||
SEMICOLON,
|
||||
PIPE,
|
||||
TAB,
|
||||
CUSTOM,
|
||||
}
|
||||
@ -0,0 +1,41 @@
|
||||
namespace AIStudio.Assistants.BatchProcessing;
|
||||
|
||||
public static class BatchProcessingCsvSeparatorExtensions
|
||||
{
|
||||
private const char DEFAULT_SEPARATOR = ';';
|
||||
|
||||
private static string TB(string fallbackEN) => Tools.PluginSystem.I18N.I.T(fallbackEN, typeof(BatchProcessingCsvSeparatorExtensions).Namespace, nameof(BatchProcessingCsvSeparatorExtensions));
|
||||
|
||||
public static string Name(this BatchProcessingCsvSeparator separator) => separator switch
|
||||
{
|
||||
BatchProcessingCsvSeparator.COMMA => TB("Comma (,)"),
|
||||
BatchProcessingCsvSeparator.SEMICOLON => TB("Semicolon (;)"),
|
||||
BatchProcessingCsvSeparator.PIPE => TB("Vertical bar (|)"),
|
||||
BatchProcessingCsvSeparator.TAB => TB("Tab"),
|
||||
BatchProcessingCsvSeparator.CUSTOM => TB("Custom character"),
|
||||
|
||||
_ => TB("Unknown"),
|
||||
};
|
||||
|
||||
public static char Character(this BatchProcessingCsvSeparator separator, string customSeparator) => separator switch
|
||||
{
|
||||
BatchProcessingCsvSeparator.COMMA => ',',
|
||||
BatchProcessingCsvSeparator.SEMICOLON => ';',
|
||||
BatchProcessingCsvSeparator.PIPE => '|',
|
||||
BatchProcessingCsvSeparator.TAB => '\t',
|
||||
BatchProcessingCsvSeparator.CUSTOM when IsValidCustomSeparator(customSeparator) => customSeparator[0],
|
||||
|
||||
_ => DEFAULT_SEPARATOR,
|
||||
};
|
||||
|
||||
internal static bool IsValidCustomSeparator(string separator)
|
||||
{
|
||||
if (string.IsNullOrEmpty(separator) || separator.Length is not 1)
|
||||
return false;
|
||||
|
||||
var character = separator[0];
|
||||
return !char.IsLetterOrDigit(character)
|
||||
&& !char.IsWhiteSpace(character)
|
||||
&& character is not '"' and not '\r' and not '\n';
|
||||
}
|
||||
}
|
||||
@ -334,6 +334,9 @@ UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::ASSISTANTBASE::T83711157"] = "Copy result
|
||||
-- The transcription provider returned an empty transcript.
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING::T1080540822"] = "The transcription provider returned an empty transcript."
|
||||
|
||||
-- We always write a semicolon-separated log named log.csv, which lists every document with its processing time, the model, the status, and the details of any error. 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.
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING::T1124333059"] = "We always write a semicolon-separated log named log.csv, which lists every document with its processing time, the model, the status, and the details of any error. 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."
|
||||
|
||||
-- Name of the results table (optional)
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING::T1127380661"] = "Name of the results table (optional)"
|
||||
|
||||
@ -382,6 +385,9 @@ UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING
|
||||
-- No matching files were found in the selected folder.
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING::T1528532808"] = "No matching files were found in the selected folder."
|
||||
|
||||
-- Custom column separator
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING::T1547654319"] = "Custom column separator"
|
||||
|
||||
-- Select the output folder
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING::T1598970341"] = "Select the output folder"
|
||||
|
||||
@ -421,9 +427,6 @@ UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING
|
||||
-- Configured instructions file: {0}
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING::T2215428124"] = "Configured instructions file: {0}"
|
||||
|
||||
-- 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.
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING::T225540173"] = "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."
|
||||
|
||||
-- No usable transcription provider is configured.
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING::T2282521655"] = "No usable transcription provider is configured."
|
||||
|
||||
@ -466,6 +469,9 @@ UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING
|
||||
-- The batch run was canceled.
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING::T2641642683"] = "The batch run was canceled."
|
||||
|
||||
-- Choose which character separates the columns of the results table.
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING::T2642486086"] = "Choose which character separates the columns of the results table."
|
||||
|
||||
-- The configured instructions file no longer exists.
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING::T2652734495"] = "The configured instructions file no longer exists."
|
||||
|
||||
@ -481,6 +487,9 @@ UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING
|
||||
-- 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.
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING::T2724126639"] = "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."
|
||||
|
||||
-- Please enter exactly one punctuation or symbol character. Letters, numbers, spaces, quotation marks, and line breaks cannot be used as CSV separators.
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING::T2908365499"] = "Please enter exactly one punctuation or symbol character. Letters, numbers, spaces, quotation marks, and line breaks cannot be used as CSV separators."
|
||||
|
||||
-- Was not able to write the result file: {0}
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING::T2991295581"] = "Was not able to write the result file: {0}"
|
||||
|
||||
@ -547,12 +556,18 @@ UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING
|
||||
-- Was not able to extract any text from this file.
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING::T4175885324"] = "Was not able to extract any text from this file."
|
||||
|
||||
-- Column separator
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING::T423947932"] = "Column separator"
|
||||
|
||||
-- The configured instructions file could not be read.
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING::T4274794480"] = "The configured instructions file could not be read."
|
||||
|
||||
-- Progress
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING::T439787878"] = "Progress"
|
||||
|
||||
-- Enter one punctuation or symbol character.
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING::T469253621"] = "Enter one punctuation or symbol character."
|
||||
|
||||
-- No, only process files in the selected folder
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING::T49675965"] = "No, only process files in the selected folder"
|
||||
|
||||
@ -583,6 +598,24 @@ UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING
|
||||
-- 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.
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING::T822136905"] = "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."
|
||||
|
||||
-- Comma (,)
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::BATCHPROCESSINGCSVSEPARATOREXTENSIONS::T1676507543"] = "Comma (,)"
|
||||
|
||||
-- Semicolon (;)
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::BATCHPROCESSINGCSVSEPARATOREXTENSIONS::T3267990938"] = "Semicolon (;)"
|
||||
|
||||
-- Unknown
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::BATCHPROCESSINGCSVSEPARATOREXTENSIONS::T3424652889"] = "Unknown"
|
||||
|
||||
-- Tab
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::BATCHPROCESSINGCSVSEPARATOREXTENSIONS::T4219689196"] = "Tab"
|
||||
|
||||
-- Vertical bar (|)
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::BATCHPROCESSINGCSVSEPARATOREXTENSIONS::T4252399493"] = "Vertical bar (|)"
|
||||
|
||||
-- Custom character
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::BATCHPROCESSINGCSVSEPARATOREXTENSIONS::T719177757"] = "Custom character"
|
||||
|
||||
-- One CSV results table, where each answer becomes one row
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::BATCHPROCESSINGOUTPUTMODEEXTENSIONS::T1515293131"] = "One CSV results table, where each answer becomes one row"
|
||||
|
||||
@ -6523,6 +6556,9 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGBATCHPROCESSING::T17
|
||||
-- Batch processing options are preselected
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGBATCHPROCESSING::T1893713430"] = "Batch processing options are preselected"
|
||||
|
||||
-- Default custom column separator
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGBATCHPROCESSING::T19367494"] = "Default custom column separator"
|
||||
|
||||
-- Default document analysis policy
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGBATCHPROCESSING::T2015391667"] = "Default document analysis policy"
|
||||
|
||||
@ -6547,9 +6583,15 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGBATCHPROCESSING::T26
|
||||
-- Input
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGBATCHPROCESSING::T2677268763"] = "Input"
|
||||
|
||||
-- Default column separator
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGBATCHPROCESSING::T2745158463"] = "Default column separator"
|
||||
|
||||
-- Preselect batch processing options?
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGBATCHPROCESSING::T2849251744"] = "Preselect batch processing options?"
|
||||
|
||||
-- Please enter exactly one punctuation or symbol character. Letters, numbers, spaces, quotation marks, and line breaks cannot be used as CSV separators.
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGBATCHPROCESSING::T2908365499"] = "Please enter exactly one punctuation or symbol character. Letters, numbers, spaces, quotation marks, and line breaks cannot be used as CSV separators."
|
||||
|
||||
-- Default file patterns
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGBATCHPROCESSING::T2909693903"] = "Default file patterns"
|
||||
|
||||
@ -6592,12 +6634,18 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGBATCHPROCESSING::T40
|
||||
-- The configured default policy no longer exists. Select another policy before starting a policy-based batch run.
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGBATCHPROCESSING::T438852523"] = "The configured default policy no longer exists. Select another policy before starting a policy-based batch run."
|
||||
|
||||
-- Enter one punctuation or symbol character.
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGBATCHPROCESSING::T469253621"] = "Enter one punctuation or symbol character."
|
||||
|
||||
-- Select the default Markdown instructions file
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGBATCHPROCESSING::T470691525"] = "Select the default Markdown instructions file"
|
||||
|
||||
-- Assistant: Batch Processing defaults
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGBATCHPROCESSING::T481452904"] = "Assistant: Batch Processing defaults"
|
||||
|
||||
-- Choose which character separates the columns of new results tables.
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGBATCHPROCESSING::T573962596"] = "Choose which character separates the columns of new results tables."
|
||||
|
||||
-- Default output mode
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGBATCHPROCESSING::T601648878"] = "Default output mode"
|
||||
|
||||
|
||||
@ -15,6 +15,7 @@
|
||||
AutoGrow="@this.AutoGrow"
|
||||
MaxLines="@this.GetMaxLines"
|
||||
Immediate="@true"
|
||||
Validation="@this.Validation"
|
||||
Underline="false"
|
||||
/>
|
||||
}
|
||||
@ -34,6 +35,7 @@ else
|
||||
AutoGrow="@this.AutoGrow"
|
||||
MaxLines="@this.GetMaxLines"
|
||||
Immediate="@true"
|
||||
Validation="@this.Validation"
|
||||
Underline="false"
|
||||
Class="flex-grow-1"
|
||||
/>
|
||||
@ -41,4 +43,4 @@ else
|
||||
@this.ResetButtonText
|
||||
</MudButton>
|
||||
</MudStack>
|
||||
}
|
||||
}
|
||||
@ -53,6 +53,12 @@ public partial class ConfigurationText : ConfigurationBaseCore
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public string ResetButtonText { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Validates the configured text before it is stored.
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public Func<string, string?>? Validation { get; set; }
|
||||
|
||||
private string internalText = string.Empty;
|
||||
private readonly Timer timer = new(TimeSpan.FromMilliseconds(500))
|
||||
@ -69,10 +75,6 @@ public partial class ConfigurationText : ConfigurationBaseCore
|
||||
|
||||
protected override string Label => this.OptionDescription;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Overrides of ConfigurationBase
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
this.timer.Elapsed += async (_, _) => await this.InvokeAsync(async () => await this.OptionChanged(this.internalText));
|
||||
@ -110,6 +112,9 @@ public partial class ConfigurationText : ConfigurationBaseCore
|
||||
|
||||
private async Task OptionChanged(string updatedText)
|
||||
{
|
||||
if (this.Validation?.Invoke(updatedText) is not null)
|
||||
return;
|
||||
|
||||
this.TextUpdate(updatedText);
|
||||
await this.SettingsManager.StoreSettings();
|
||||
await this.InformAboutChange();
|
||||
|
||||
@ -47,6 +47,11 @@
|
||||
{
|
||||
<ConfigurationText OptionDescription="@T("Default results table name")" Disabled="@this.DefaultsDisabled" Icon="@Icons.Material.Filled.Description" Text="@(() => this.SettingsManager.ConfigurationData.BatchProcessing.CsvFileName)" TextUpdate="@(value => this.SettingsManager.ConfigurationData.BatchProcessing.CsvFileName = value)" IsLocked="() => ManagedConfiguration.TryGet(x => x.BatchProcessing, x => x.CsvFileName, out var meta) && meta.IsLocked"/>
|
||||
<ConfigurationText OptionDescription="@T("Default result column header")" Disabled="@this.DefaultsDisabled" Icon="@Icons.Material.Filled.TableChart" Text="@(() => this.SettingsManager.ConfigurationData.BatchProcessing.ResultColumnHeader)" TextUpdate="@(value => this.SettingsManager.ConfigurationData.BatchProcessing.ResultColumnHeader = value)" IsLocked="() => ManagedConfiguration.TryGet(x => x.BatchProcessing, x => x.ResultColumnHeader, out var meta) && meta.IsLocked"/>
|
||||
<ConfigurationSelect OptionDescription="@T("Default column separator")" Disabled="@this.DefaultsDisabled" SelectedValue="@(() => this.SettingsManager.ConfigurationData.BatchProcessing.CsvSeparator)" Data="@this.CsvSeparatorData" SelectionUpdate="@(value => this.SettingsManager.ConfigurationData.BatchProcessing.CsvSeparator = value)" OptionHelp="@T("Choose which character separates the columns of new results tables.")" IsLocked="() => ManagedConfiguration.TryGet(x => x.BatchProcessing, x => x.CsvSeparator, out var meta) && meta.IsLocked"/>
|
||||
@if (this.SettingsManager.ConfigurationData.BatchProcessing.CsvSeparator is BatchProcessingCsvSeparator.CUSTOM)
|
||||
{
|
||||
<ConfigurationText OptionDescription="@T("Default custom column separator")" Disabled="@this.DefaultsDisabled" Icon="@Icons.Material.Filled.Edit" Text="@(() => this.SettingsManager.ConfigurationData.BatchProcessing.CustomCsvSeparator)" TextUpdate="@(value => this.SettingsManager.ConfigurationData.BatchProcessing.CustomCsvSeparator = value)" Validation="@this.ValidateCustomCsvSeparator" OptionHelp="@T("Enter one punctuation or symbol character.")" IsLocked="() => ManagedConfiguration.TryGet(x => x.BatchProcessing, x => x.CustomCsvSeparator, out var meta) && meta.IsLocked"/>
|
||||
}
|
||||
}
|
||||
<ConfigurationDirectory OptionDescription="@T("Default output folder")" Disabled="@this.DefaultsDisabled" DirectoryDialogTitle="@T("Select the default output folder")" Text="@(() => this.SettingsManager.ConfigurationData.BatchProcessing.OutputDirectory)" TextUpdate="@(value => this.SettingsManager.ConfigurationData.BatchProcessing.OutputDirectory = value)" OptionHelp="@T("Leave empty to use the ai-results subfolder of the input folder.")" IsLocked="() => ManagedConfiguration.TryGet(x => x.BatchProcessing, x => x.OutputDirectory, out var meta) && meta.IsLocked"/>
|
||||
|
||||
|
||||
@ -45,6 +45,21 @@ public partial class SettingsDialogBatchProcessing : SettingsDialogBase
|
||||
.Select(value => new ConfigurationSelectData<BatchProcessingOutputMode>(value.Name(), value))
|
||||
];
|
||||
|
||||
private IReadOnlyList<ConfigurationSelectData<BatchProcessingCsvSeparator>> CsvSeparatorData =>
|
||||
[
|
||||
.. Enum
|
||||
.GetValues<BatchProcessingCsvSeparator>()
|
||||
.Select(value => new ConfigurationSelectData<BatchProcessingCsvSeparator>(value.Name(), value))
|
||||
];
|
||||
|
||||
private string? ValidateCustomCsvSeparator(string separator)
|
||||
{
|
||||
if (!BatchProcessingCsvSeparatorExtensions.IsValidCustomSeparator(separator))
|
||||
return T("Please enter exactly one punctuation or symbol character. Letters, numbers, spaces, quotation marks, and line breaks cannot be used as CSV separators.");
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private IReadOnlyList<ConfigurationSelectData<string>> PolicyData
|
||||
{
|
||||
get
|
||||
|
||||
@ -424,6 +424,10 @@ CONFIG["SETTINGS"] = {}
|
||||
-- CONFIG["SETTINGS"]["DataBatchProcessing.OutputMode"] = "MARKDOWN_FILES"
|
||||
-- CONFIG["SETTINGS"]["DataBatchProcessing.CsvFileName"] = "batch-results.csv"
|
||||
-- CONFIG["SETTINGS"]["DataBatchProcessing.ResultColumnHeader"] = "Result"
|
||||
-- Allowed CSV separator values are: COMMA, SEMICOLON, PIPE, TAB, CUSTOM
|
||||
-- A custom separator must be exactly one punctuation or symbol character.
|
||||
-- CONFIG["SETTINGS"]["DataBatchProcessing.CsvSeparator"] = "SEMICOLON"
|
||||
-- CONFIG["SETTINGS"]["DataBatchProcessing.CustomCsvSeparator"] = "^"
|
||||
--
|
||||
-- Configure the minimum provider confidence and the default provider.
|
||||
-- Allowed confidence values are: NONE, UNTRUSTED, UNKNOWN, VERY_LOW, LOW, MODERATE, MEDIUM, HIGH
|
||||
@ -444,6 +448,8 @@ CONFIG["SETTINGS"] = {}
|
||||
-- CONFIG["SETTINGS"]["DataBatchProcessing.OutputMode.AllowUserOverride"] = true
|
||||
-- CONFIG["SETTINGS"]["DataBatchProcessing.CsvFileName.AllowUserOverride"] = true
|
||||
-- CONFIG["SETTINGS"]["DataBatchProcessing.ResultColumnHeader.AllowUserOverride"] = true
|
||||
-- CONFIG["SETTINGS"]["DataBatchProcessing.CsvSeparator.AllowUserOverride"] = true
|
||||
-- CONFIG["SETTINGS"]["DataBatchProcessing.CustomCsvSeparator.AllowUserOverride"] = true
|
||||
-- CONFIG["SETTINGS"]["DataBatchProcessing.MinimumProviderConfidence.AllowUserOverride"] = true
|
||||
-- CONFIG["SETTINGS"]["DataBatchProcessing.PreselectedProvider.AllowUserOverride"] = true
|
||||
|
||||
|
||||
@ -44,6 +44,10 @@ public sealed class DataBatchProcessing(Expression<Func<Data, DataBatchProcessin
|
||||
|
||||
public string ResultColumnHeader { get; set; } = ManagedConfiguration.Register(configSelection, value => value.ResultColumnHeader, string.Empty);
|
||||
|
||||
public BatchProcessingCsvSeparator CsvSeparator { get; set; } = ManagedConfiguration.Register(configSelection, value => value.CsvSeparator, BatchProcessingCsvSeparator.SEMICOLON);
|
||||
|
||||
public string CustomCsvSeparator { get; set; } = ManagedConfiguration.Register(configSelection, value => value.CustomCsvSeparator, string.Empty);
|
||||
|
||||
public ConfidenceLevel MinimumProviderConfidence { get; set; } = ManagedConfiguration.Register(configSelection, value => value.MinimumProviderConfidence, ConfidenceLevel.NONE);
|
||||
|
||||
public string PreselectedProvider { get; set; } = ManagedConfiguration.Register(configSelection, value => value.PreselectedProvider, string.Empty);
|
||||
|
||||
@ -350,6 +350,8 @@ public sealed class PluginConfiguration(bool isInternal, LuaState state, PluginT
|
||||
ManagedConfiguration.TryProcessConfiguration(x => x.BatchProcessing, x => x.OutputMode, this.Id, settingsTable, dryRun);
|
||||
ManagedConfiguration.TryProcessConfiguration(x => x.BatchProcessing, x => x.CsvFileName, this.Id, settingsTable, dryRun);
|
||||
ManagedConfiguration.TryProcessConfiguration(x => x.BatchProcessing, x => x.ResultColumnHeader, this.Id, settingsTable, dryRun);
|
||||
ManagedConfiguration.TryProcessConfiguration(x => x.BatchProcessing, x => x.CsvSeparator, this.Id, settingsTable, dryRun);
|
||||
ManagedConfiguration.TryProcessConfiguration(x => x.BatchProcessing, x => x.CustomCsvSeparator, this.Id, settingsTable, dryRun);
|
||||
ManagedConfiguration.TryProcessConfiguration(x => x.BatchProcessing, x => x.MinimumProviderConfidence, this.Id, settingsTable, dryRun);
|
||||
ManagedConfiguration.TryProcessConfiguration(x => x.BatchProcessing, x => x.PreselectedProvider, Guid.Empty, this.Id, settingsTable, dryRun);
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user