Rework of the file export (#894)

Co-authored-by: Thorsten Sommer <SommerEngineering@users.noreply.github.com>
This commit is contained in:
nilskruthoffandThorsten Sommer authored and GitHub committed 2026-08-31 08:40:50 +02:00
1 parent f14c69b938
commit c66a61713d
33 files changed
+1212 -266

No files matched your search

@@ -75,9 +75,9 @@
<div id="@BEFORE_RESULT_DIV_ID" class="mt-3">
</div>
@if (this.ShowResult && !this.ShowEntireChatThread && this.ResultingContentBlock is not null && this.ResultingContentBlock.Content is not null)
@if (this.ShowResult && !this.ShowEntireChatThread && this.ResultingContentBlock?.Content != null)
{
<ContentBlockComponent Role="@(this.ResultingContentBlock.Role)" Type="@(this.ResultingContentBlock.ContentType)" Time="@(this.ResultingContentBlock.Time)" Content="@this.ResultingContentBlock.Content"/>
<ContentBlockComponent Role="@(this.ResultingContentBlock.Role)" Type="@(this.ResultingContentBlock.ContentType)" Time="@(this.ResultingContentBlock.Time)" Content="@this.ResultingContentBlock.Content" ExportTitle="@TB("Export result")"/>
}
@if(this.ShowResult && this.ShowEntireChatThread && this.ChatThread is not null)
@@ -86,7 +86,7 @@
{
@if (block is { HideFromUser: false, Content: not null })
{
<ContentBlockComponent Role="@block.Role" Type="@block.ContentType" Time="@block.Time" Content="@block.Content"/>
<ContentBlockComponent Role="@block.Role" Type="@block.ContentType" Time="@block.Time" Content="@block.Content" ExportTitle="@TB("Export result")"/>
}
}
}
@@ -115,10 +115,19 @@ else
}
</MudSelect>
@if (this.outputMode is BatchProcessingOutputMode.MARKDOWN_FILES)
@if (this.outputMode is BatchProcessingOutputMode.INDIVIDUAL_FILES)
{
<MudSelect T="FileExportFormat" @bind-Value="@this.resultFileFormat" Disabled="@this.isProcessingBatch" AdornmentIcon="@Icons.Material.Filled.Description" Adornment="Adornment.Start" Label="@T("File format")" HelperText="@T("Choose the format of the result files. Everything except Markdown is converted by Pandoc, which AI Studio offers to install when it is missing.")" Variant="Variant.Outlined" Margin="Margin.Dense" Class="mb-3">
@foreach (var format in FileExportFormatExtensions.ANSWER_FORMATS)
{
<MudSelectItem Value="@format">
@format.ToName()
</MudSelectItem>
}
</MudSelect>
<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.")
@(string.Format(T("Each answer is stored as its own file. Those files are named after the document, e.g., the answer for report.pdf is stored as report_result{0}."), this.resultFileFormat.ToFileExtension()))
</MudJustifiedText>
}
else
@@ -71,8 +71,8 @@ public partial class AssistantBatchProcessing
/// <summary>
/// Checks whether a document can be restored from the previous run. Beyond
/// the log entry, the result of the previous run must still exist: in the
/// table mode the answer within the results table, in the Markdown mode the
/// result file. Without the result, restoring would mark the document as
/// table mode the answer within the results table, in the individual file
/// mode the result file. Without the result, restoring would mark the document as
/// done while its answer is lost, so we process it again instead.
/// </summary>
private bool CanRestoreFromPreviousRun(string relativePath, string resolvedOutputDirectory, Dictionary<string, BatchProcessingLogEntry> previousLog, Dictionary<string, string> previousResults, out BatchProcessingLogEntry? logEntry)
@@ -106,9 +106,9 @@ public partial class AssistantBatchProcessing
private async Task WriteLogAsync(string resolvedOutputDirectory)
{
var sb = new StringBuilder();
sb.AppendLine(BatchProcessingCsv.ToCsvRow(LOG_SEPARATOR, T("File"), T("Time"), T("Model"), T("Status"), T("Details")));
sb.AppendLine(CsvWriter.ToRow(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(LOG_SEPARATOR, fileResult.RelativePath, fileResult.ProcessedAt.ToString(TIME_FORMAT, CultureInfo.InvariantCulture), fileResult.ModelName, fileResult.Status.ToString(), fileResult.Message));
sb.AppendLine(CsvWriter.ToRow(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());
}
@@ -120,9 +120,9 @@ public partial class AssistantBatchProcessing
{
var separator = this.csvSeparator.Character(this.customCsvSeparator);
var sb = new StringBuilder();
sb.AppendLine(BatchProcessingCsv.ToCsvRow(separator, T("File"), this.ResultColumnHeader));
sb.AppendLine(CsvWriter.ToRow(separator, T("File"), this.ResultColumnHeader));
foreach (var fileResult in this.fileResults.Where(x => x.Status is BatchProcessingFileStatus.DONE))
sb.AppendLine(BatchProcessingCsv.ToCsvRow(separator, fileResult.RelativePath, fileResult.ResultText));
sb.AppendLine(CsvWriter.ToRow(separator, fileResult.RelativePath, fileResult.ResultText));
await this.WriteCsvFileAsync(Path.Join(resolvedOutputDirectory, this.ResolveResultsFileName()), sb.ToString());
}
@@ -232,7 +232,7 @@ public partial class AssistantBatchProcessing
}
/// <summary>
/// Creates the name of the Markdown result file for one document.
/// Creates the name of the result file for one document, in the chosen file format.
/// </summary>
/// <remarks>
/// Two documents of the same run may share their name and differ only in
@@ -242,13 +242,14 @@ public partial class AssistantBatchProcessing
/// </remarks>
private string CreateResultFileName(string sourceFileName)
{
var extension = this.resultFileFormat.ToFileExtension();
var stem = Path.GetFileNameWithoutExtension(sourceFileName);
var candidate = $"{stem}{RESULT_FILE_SUFFIX}";
var candidate = $"{stem}{RESULT_FILE_SUFFIX}{extension}";
var counter = 2;
while (!this.usedResultFileNames.Add(candidate))
{
candidate = $"{stem}_result_{counter}.md";
candidate = $"{stem}{RESULT_FILE_SUFFIX}_{counter}{extension}";
counter++;
}
@@ -1,6 +1,5 @@
using System.Diagnostics;
using System.Globalization;
using System.Text;
namespace AIStudio.Assistants.BatchProcessing;
@@ -14,6 +13,19 @@ public partial class AssistantBatchProcessing
var (resolvedOutputDirectory, files) = runPreparation.Value;
//
// Every format but Markdown is written by Pandoc, so it has to be there before the first
// document. Asking per document would put the installation dialog in front of the user
// hundreds of times, and starting without it would spend time and tokens on answers we
// cannot write anywhere:
//
if (this.outputMode is BatchProcessingOutputMode.INDIVIDUAL_FILES && this.resultFileFormat.UsesPandoc())
{
var pandocState = await this.PandocAvailability.EnsureAvailabilityAsync(showSuccessMessage: false, showDialog: true);
if (!pandocState.IsAvailable)
return;
}
//
// When the output folder already contains a log, a previous run was
// interrupted or produced errors. Let the user decide what to do:
@@ -63,7 +75,7 @@ public partial class AssistantBatchProcessing
if (DateTimeOffset.TryParseExact(logEntry.Time, TIME_FORMAT, CultureInfo.InvariantCulture, DateTimeStyles.AssumeLocal, out var processedAt))
fileResult.ProcessedAt = processedAt;
// Reserve the Markdown file name of the previous run, so that a
// Reserve the result file name of the previous run, so that a
// document processed now cannot overwrite that earlier result:
if (!string.IsNullOrWhiteSpace(logEntry.Details))
this.usedResultFileNames.Add(logEntry.Details);
@@ -211,12 +223,26 @@ public partial class AssistantBatchProcessing
}
fileResult.ResultText = aiAnswer;
if (this.outputMode is BatchProcessingOutputMode.MARKDOWN_FILES)
if (this.outputMode is BatchProcessingOutputMode.INDIVIDUAL_FILES)
{
try
{
var resultFilePath = Path.Join(resolvedOutputDirectory, this.CreateResultFileName(fileResult.FileName));
await File.WriteAllTextAsync(resultFilePath, aiAnswer, Encoding.UTF8, CancellationToken.None);
if (this.resultFileFormat.UsesPandoc())
{
//
// Pandoc reports a failure instead of throwing, because one document which
// cannot be converted must not end a run over hundreds of them:
//
if (!await PandocExport.ConvertAsync(this.RustService, aiAnswer, resultFilePath, this.resultFileFormat, token))
{
this.FinishFileResult(fileResult, BatchProcessingFileStatus.FAILED, T("Was not able to convert the answer into the chosen file format."));
return;
}
}
else
await File.WriteAllTextAsync(resultFilePath, aiAnswer, this.resultFileFormat.ToFileEncoding(), CancellationToken.None);
this.FinishFileResult(fileResult, BatchProcessingFileStatus.DONE, Path.GetFileName(resultFilePath));
}
catch (Exception e)
@@ -16,6 +16,7 @@ public partial class AssistantBatchProcessing
private static readonly AssistantSessionStateKey<string> PROMPT_FILE_LOAD_ISSUE_STATE_KEY = new(nameof(promptFileLoadIssue));
private static readonly AssistantSessionStateKey<DataDocumentAnalysisPolicy?> SELECTED_POLICY_STATE_KEY = new(nameof(selectedPolicy));
private static readonly AssistantSessionStateKey<BatchProcessingOutputMode> OUTPUT_MODE_STATE_KEY = new(nameof(outputMode));
private static readonly AssistantSessionStateKey<FileExportFormat> RESULT_FILE_FORMAT_STATE_KEY = new(nameof(resultFileFormat));
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));
@@ -43,6 +44,7 @@ public partial class AssistantBatchProcessing
state.Set(PROMPT_FILE_LOAD_ISSUE_STATE_KEY, this.promptFileLoadIssue);
state.Set(SELECTED_POLICY_STATE_KEY, this.selectedPolicy);
state.Set(OUTPUT_MODE_STATE_KEY, this.outputMode);
state.Set(RESULT_FILE_FORMAT_STATE_KEY, this.resultFileFormat);
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);
@@ -71,6 +73,7 @@ public partial class AssistantBatchProcessing
state.Restore(PROMPT_FILE_LOAD_ISSUE_STATE_KEY, value => this.promptFileLoadIssue = value);
state.Restore(SELECTED_POLICY_STATE_KEY, value => this.selectedPolicy = value);
state.Restore(OUTPUT_MODE_STATE_KEY, value => this.outputMode = value);
state.Restore(RESULT_FILE_FORMAT_STATE_KEY, value => this.resultFileFormat = 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);
@@ -1,6 +1,7 @@
using AIStudio.Dialogs.Settings;
using AIStudio.Provider;
using AIStudio.Settings.DataModel;
using AIStudio.Tools.Services;
using Microsoft.AspNetCore.Components;
@@ -11,10 +12,13 @@ public partial class AssistantBatchProcessing : AssistantBaseCore<SettingsDialog
[Inject]
private IDialogService DialogService { get; init; } = null!;
[Inject]
private PandocAvailabilityService PandocAvailability { get; init; } = null!;
private const string DEFAULT_OUTPUT_DIRECTORY_NAME = "ai-results";
private const string DEFAULT_RESULTS_FILENAME = "batch-results.csv";
private const string CSV_EXTENSION = ".csv";
private const string RESULT_FILE_SUFFIX = "_result.md";
private const string RESULT_FILE_SUFFIX = "_result";
private const string TRANSCRIPT_FILE_SUFFIX = ".transcript.md";
private const string TIME_FORMAT = "yyyy-MM-dd HH:mm:ss";
private const char LOG_SEPARATOR = ';';
@@ -87,7 +91,8 @@ public partial class AssistantBatchProcessing : AssistantBaseCore<SettingsDialog
private string promptFilePath = string.Empty;
private string promptFileLoadIssue = string.Empty;
private DataDocumentAnalysisPolicy? selectedPolicy;
private BatchProcessingOutputMode outputMode = BatchProcessingOutputMode.MARKDOWN_FILES;
private BatchProcessingOutputMode outputMode = BatchProcessingOutputMode.INDIVIDUAL_FILES;
private FileExportFormat resultFileFormat = FileExportFormat.MARKDOWN;
private string resultColumnHeader = string.Empty;
private string csvFileName = string.Empty;
private BatchProcessingCsvSeparator csvSeparator = BatchProcessingCsvSeparator.SEMICOLON;
@@ -160,7 +165,8 @@ public partial class AssistantBatchProcessing : AssistantBaseCore<SettingsDialog
this.freePrompt = string.Empty;
this.promptFilePath = string.Empty;
this.selectedPolicy = null;
this.outputMode = BatchProcessingOutputMode.MARKDOWN_FILES;
this.outputMode = BatchProcessingOutputMode.INDIVIDUAL_FILES;
this.resultFileFormat = FileExportFormat.MARKDOWN;
this.resultColumnHeader = string.Empty;
this.csvFileName = string.Empty;
this.csvSeparator = BatchProcessingCsvSeparator.SEMICOLON;
@@ -180,6 +186,7 @@ public partial class AssistantBatchProcessing : AssistantBaseCore<SettingsDialog
this.selectedPolicy = this.SettingsManager.ConfigurationData.DocumentAnalysis.Policies
.FirstOrDefault(policy => policy.Id == settings.PreselectedPolicyId);
this.outputMode = settings.OutputMode;
this.resultFileFormat = settings.ResultFileFormat;
this.resultColumnHeader = settings.ResultColumnHeader;
this.csvFileName = settings.CsvFileName;
this.csvSeparator = settings.CsvSeparator;
@@ -3,35 +3,13 @@ using System.Text;
namespace AIStudio.Assistants.BatchProcessing;
/// <summary>
/// Reads and writes the CSV files of the batch processing assistant. Fields
/// are quoted according to RFC 4180 using the separator selected for the
/// respective file.
/// Reads the CSV files of the batch processing assistant. Writing them is the job of CsvWriter,
/// which quotes fields according to RFC 4180 using the separator selected for the respective file.
/// </summary>
public static class BatchProcessingCsv
{
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, char separator)
{
if (string.IsNullOrEmpty(text))
return string.Empty;
// 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("\"", "\"\"")}"
""";
}
/// <summary>
/// Parses a CSV text which was written by <see cref="ToCsvRow"/>.
/// Parses a CSV text which was written by CsvWriter.ToRow.
/// </summary>
/// <remarks>
/// We parse the file ourselves instead of splitting lines, because quoted
@@ -6,9 +6,14 @@ namespace AIStudio.Assistants.BatchProcessing;
public enum BatchProcessingOutputMode
{
/// <summary>
/// One Markdown result file per processed document.
/// One result file per processed document, written in the chosen file format.
/// </summary>
MARKDOWN_FILES,
/// <remarks>
/// This must stay the first member. Enums are persisted under their name, and an unknown name
/// falls back to the default value of the enum, which is the member with the value zero. That
/// is what lets settings written before this member was renamed still land here.
/// </remarks>
INDIVIDUAL_FILES,
/// <summary>
/// A CSV results table, where each AI answer becomes one row. The content of
@@ -6,7 +6,7 @@ public static class BatchProcessingOutputModeExtensions
public static string Name(this BatchProcessingOutputMode outputMode) => outputMode switch
{
BatchProcessingOutputMode.MARKDOWN_FILES => TB("One Markdown file per document"),
BatchProcessingOutputMode.INDIVIDUAL_FILES => TB("One file per document"),
BatchProcessingOutputMode.TABLE_ONLY => TB("One CSV results table, where each answer becomes one row"),
_ => TB("Unknown output mode"),
@@ -316,6 +316,9 @@ UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::ASSISTANTBASE::T1809312323"] = "Please se
-- The assistant failed. The message is: '{0}'
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::ASSISTANTBASE::T1833836281"] = "The assistant failed. The message is: '{0}'"
-- Export result
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::ASSISTANTBASE::T1840311560"] = "Export result"
-- The media transcription was canceled.
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::ASSISTANTBASE::T241403726"] = "The media transcription was canceled."
@@ -379,6 +382,9 @@ UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING
-- Failed
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING::T1434043348"] = "Failed"
-- Choose the format of the result files. Everything except Markdown is converted by Pandoc, which AI Studio offers to install when it is missing.
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING::T1457759640"] = "Choose the format of the result files. Everything except Markdown is converted by Pandoc, which AI Studio offers to install when it is missing."
-- Please select the file which contains your instructions.
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING::T1462027716"] = "Please select the file which contains your instructions."
@@ -496,15 +502,15 @@ UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING
-- Was not able to read the log of the previous run. Continuing the run would process all documents again.
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING::T2717277840"] = "Was not able to read the log of the previous run. Continuing the run would process all documents again."
-- 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."
-- Before the next file starts, AI Studio waits for a random number of whole seconds from this interval. The minimum is always 6 seconds and the maximum is 300 seconds (5 minutes). Restored files and the end of a run do not add another pause.
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING::T2742154256"] = "Before the next file starts, AI Studio waits for a random number of whole seconds from this interval. The minimum is always 6 seconds and the maximum is 300 seconds (5 minutes). Restored files and the end of a run do not add another pause."
-- 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 convert the answer into the chosen file format.
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING::T2949919602"] = "Was not able to convert the answer into the chosen file format."
-- 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}"
@@ -583,9 +589,15 @@ UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING
-- The configured instructions file could not be read.
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING::T4274794480"] = "The configured instructions file could not be read."
-- Each answer is stored as its own file. Those files are named after the document, e.g., the answer for report.pdf is stored as report_result{0}.
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING::T428878781"] = "Each answer is stored as its own file. Those files are named after the document, e.g., the answer for report.pdf is stored as report_result{0}."
-- Progress
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING::T439787878"] = "Progress"
-- File format
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING::T450269462"] = "File format"
-- Enter one punctuation or symbol character.
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING::T469253621"] = "Enter one punctuation or symbol character."
@@ -637,15 +649,15 @@ UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::BATCHPROCESSINGCSVSEPARA
-- Custom character
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::BATCHPROCESSINGCSVSEPARATOREXTENSIONS::T719177757"] = "Custom character"
-- One file per document
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::BATCHPROCESSINGOUTPUTMODEEXTENSIONS::T1430232553"] = "One file per document"
-- 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"
-- Unknown output mode
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::BATCHPROCESSINGOUTPUTMODEEXTENSIONS::T2013180377"] = "Unknown output mode"
-- One Markdown file per document
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::BATCHPROCESSINGOUTPUTMODEEXTENSIONS::T2420177746"] = "One Markdown file per document"
-- Use a free prompt
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::BATCHPROCESSINGPROMPTSOURCEEXTENSIONS::T1144335"] = "Use a free prompt"
@@ -3175,6 +3187,9 @@ UI_TEXT_CONTENT["AISTUDIO::CHAT::CHATROLEEXTENSIONS::T601166687"] = "AI"
-- Edit Message
UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T1183581066"] = "Edit Message"
-- Table {0} ({1})
UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T1340759627"] = "Table {0} ({1})"
-- Do you really want to remove this message?
UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T1347427447"] = "Do you really want to remove this message?"
@@ -3199,6 +3214,12 @@ UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T2093355991"] = "Removes
-- Regenerate Message
UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T2308444540"] = "Regenerate Message"
-- Failed to export this message, because the file format '{0}' is unknown.
UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T2544592344"] = "Failed to export this message, because the file format '{0}' is unknown."
-- Export AI response
UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T2822776450"] = "Export AI response"
-- Number of attachments
UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T3018847255"] = "Number of attachments"
@@ -3220,9 +3241,6 @@ UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T4070211974"] = "Remove
-- No, keep it
UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T4188329028"] = "No, keep it"
-- Export Chat to Microsoft Word
UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T861873672"] = "Export Chat to Microsoft Word"
-- The file '{0}' is currently not available and was not sent.
UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTTEXT::T1432544573"] = "The file '{0}' is currently not available and was not sent."
@@ -6862,6 +6880,9 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGBATCHPROCESSING::T26
-- Default column separator
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGBATCHPROCESSING::T2745158463"] = "Default column separator"
-- Choose the format of new result files. Everything except Markdown is converted by Pandoc.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGBATCHPROCESSING::T2760965660"] = "Choose the format of new result files. Everything except Markdown is converted by Pandoc."
-- Preselect batch processing options?
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGBATCHPROCESSING::T2849251744"] = "Preselect batch processing options?"
@@ -6919,6 +6940,9 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGBATCHPROCESSING::T39
-- Output
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGBATCHPROCESSING::T4000727844"] = "Output"
-- Default file format
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGBATCHPROCESSING::T4046754119"] = "Default file format"
-- 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."
@@ -9967,6 +9991,30 @@ UI_TEXT_CONTENT["AISTUDIO::TOOLS::EXTERNALHTTPCLIENTTIMEOUT::T599774443"] = "The
-- policy files
UI_TEXT_CONTENT["AISTUDIO::TOOLS::EXTERNALHTTPCLIENTTIMEOUT::T632340680"] = "policy files"
-- OpenDocument Text (.odt), e.g. LibreOffice
UI_TEXT_CONTENT["AISTUDIO::TOOLS::FILEEXPORTFORMATEXTENSIONS::T1612025407"] = "OpenDocument Text (.odt), e.g. LibreOffice"
-- LaTeX (.tex)
UI_TEXT_CONTENT["AISTUDIO::TOOLS::FILEEXPORTFORMATEXTENSIONS::T2233607007"] = "LaTeX (.tex)"
-- Markdown (.md)
UI_TEXT_CONTENT["AISTUDIO::TOOLS::FILEEXPORTFORMATEXTENSIONS::T2319970170"] = "Markdown (.md)"
-- Table (.tsv)
UI_TEXT_CONTENT["AISTUDIO::TOOLS::FILEEXPORTFORMATEXTENSIONS::T293798559"] = "Table (.tsv)"
-- Microsoft Word (.docx)
UI_TEXT_CONTENT["AISTUDIO::TOOLS::FILEEXPORTFORMATEXTENSIONS::T3054800422"] = "Microsoft Word (.docx)"
-- Webpage (.html)
UI_TEXT_CONTENT["AISTUDIO::TOOLS::FILEEXPORTFORMATEXTENSIONS::T3651679344"] = "Webpage (.html)"
-- Table (.csv)
UI_TEXT_CONTENT["AISTUDIO::TOOLS::FILEEXPORTFORMATEXTENSIONS::T530872684"] = "Table (.csv)"
-- Unknown format
UI_TEXT_CONTENT["AISTUDIO::TOOLS::FILEEXPORTFORMATEXTENSIONS::T677355172"] = "Unknown format"
-- The file type of '{0}' could not be determined, so the file was not sent.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::FILEEXTRACTIONRESULTEXTENSIONS::T1459702734"] = "The file type of '{0}' could not be determined, so the file was not sent."
@@ -10069,17 +10117,20 @@ UI_TEXT_CONTENT["AISTUDIO::TOOLS::PANDOC::T695293525"] = "AI Studio couldn't fin
-- AI Studio couldn't install Pandoc.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PANDOC::T932858631"] = "AI Studio couldn't install Pandoc."
-- Pandoc is required for Microsoft Word export.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PANDOCEXPORT::T1473115556"] = "Pandoc is required for Microsoft Word export."
-- The export succeeded.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PANDOCEXPORT::T1713926719"] = "The export succeeded."
-- Pandoc Installation
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PANDOCEXPORT::T185447014"] = "Pandoc Installation"
-- The export failed.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PANDOCEXPORT::T1895034475"] = "The export failed."
-- Error during Microsoft Word export
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PANDOCEXPORT::T3290596792"] = "Error during Microsoft Word export"
-- Only text messages can be exported.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PANDOCEXPORT::T3576815370"] = "Only text messages can be exported."
-- Microsoft Word export successful
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PANDOCEXPORT::T4256043333"] = "Microsoft Word export successful"
-- The export succeeded.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLAINFILEEXPORT::T1713926719"] = "The export succeeded."
-- The export failed.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLAINFILEEXPORT::T1895034475"] = "The export failed."
-- Text
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::ASSISTANTS::DATAMODEL::ASSISTANTCOMPONENTTYPEEXTENSIONS::T1041509726"] = "Text"
@@ -10876,8 +10927,8 @@ UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::MEDIATRANSCRIPTIONSERVICE::T63285243
-- Pandoc Installation
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::PANDOCAVAILABILITYSERVICE::T185447014"] = "Pandoc Installation"
-- Pandoc may be required for importing files.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::PANDOCAVAILABILITYSERVICE::T2596465560"] = "Pandoc may be required for importing files."
-- AI Studio needs Pandoc for this, but it is not available.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::PANDOCAVAILABILITYSERVICE::T2610026134"] = "AI Studio needs Pandoc for this, but it is not available."
-- This plugin archive declares itself as managed by a config server. Only the IT department of your organization might deploy such plugins.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::PLUGININSTALLSERVICE::T1138181282"] = "This plugin archive declares itself as managed by a config server. Only the IT department of your organization might deploy such plugins."
@@ -11095,9 +11146,6 @@ UI_TEXT_CONTENT["AISTUDIO::TOOLS::SOURCEEXTENSIONS::T4174900468"] = "Sources pro
-- Sources provided by the AI
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SOURCEEXTENSIONS::T4261248356"] = "Sources provided by the AI"
-- Pandoc Installation
UI_TEXT_CONTENT["AISTUDIO::TOOLS::USERFILE::T185447014"] = "Pandoc Installation"
-- The file path is null or empty and the file therefore can not be loaded.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::USERFILE::T932243993"] = "The file path is null or empty and the file therefore can not be loaded."
@@ -5,6 +5,7 @@ using AIStudio.Chat;
using AIStudio.Dialogs;
using AIStudio.Dialogs.Settings;
using AIStudio.Tools.AssistantSessions;
using AIStudio.Tools.Services;
using Microsoft.AspNetCore.Components;
#if !DEBUG
@@ -28,6 +29,9 @@ public partial class AssistantPromptOptimizer : AssistantBaseCore<SettingsDialog
[Inject]
private IDialogService DialogService { get; init; } = null!;
[Inject]
private PandocAvailabilityService PandocAvailability { get; init; } = null!;
protected override Tools.Components Component => Tools.Components.PROMPT_OPTIMIZER_ASSISTANT;
protected override string Title => T("Prompt Optimizer");
@@ -581,7 +585,7 @@ public partial class AssistantPromptOptimizer : AssistantBaseCore<SettingsDialog
this.isLoadingCustomPromptGuide = true;
// A failure was already reported by UserFile.LoadFileData, so we only keep the content:
var extraction = await UserFile.LoadFileData(fileAttachment.FilePath, this.RustService, this.DialogService);
var extraction = await UserFile.LoadFileData(fileAttachment.FilePath, this.RustService, this.PandocAvailability);
this.customPromptingGuidelineContent = extraction.HasUsableContent ? extraction.Content : string.Empty;
}
catch