This commit is contained in:
nilskruthoff 2026-08-07 12:27:17 +02:00 committed by GitHub
commit ec8046ba62
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 407 additions and 90 deletions

View File

@ -2824,6 +2824,9 @@ UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T1347427447"] = "Do you
-- Yes, remove the AI response and edit it -- Yes, remove the AI response and edit it
UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T1350385882"] = "Yes, remove the AI response and edit it" UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T1350385882"] = "Yes, remove the AI response and edit it"
-- Webpage
UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T1578631058"] = "Webpage"
-- Yes, regenerate it -- Yes, regenerate it
UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T1603883875"] = "Yes, regenerate it" UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T1603883875"] = "Yes, regenerate it"
@ -2839,18 +2842,39 @@ UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T2018431076"] = "Do you
-- Removes this block -- Removes this block
UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T2093355991"] = "Removes this block" UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T2093355991"] = "Removes this block"
-- LaTeX (.tex)
UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T2233607007"] = "LaTeX (.tex)"
-- OpenDocument Text (.odt)
UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T2253393351"] = "OpenDocument Text (.odt)"
-- Regenerate Message -- Regenerate Message
UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T2308444540"] = "Regenerate Message" UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T2308444540"] = "Regenerate Message"
-- Markdown (.md)
UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T2319970170"] = "Markdown (.md)"
-- Hypertext (.html)
UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T2504486680"] = "Hypertext (.html)"
-- CSV (.csv)
UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T2853636652"] = "CSV (.csv)"
-- Number of attachments -- Number of attachments
UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T3018847255"] = "Number of attachments" UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T3018847255"] = "Number of attachments"
-- Microsoft Word (.docx)
UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T3054800422"] = "Microsoft Word (.docx)"
-- Cannot render content of type {0} yet. -- Cannot render content of type {0} yet.
UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T3175548294"] = "Cannot render content of type {0} yet." UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T3175548294"] = "Cannot render content of type {0} yet."
-- Edit -- Edit
UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T3267849393"] = "Edit" UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T3267849393"] = "Edit"
-- Failed to export document to unknown file format '{0}'.
UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T3566938024"] = "Failed to export document to unknown file format '{0}'."
-- Regenerate -- Regenerate
UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T3587744975"] = "Regenerate" UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T3587744975"] = "Regenerate"
@ -2863,8 +2887,11 @@ UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T4070211974"] = "Remove
-- No, keep it -- No, keep it
UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T4188329028"] = "No, keep it" UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T4188329028"] = "No, keep it"
-- Export Chat to Microsoft Word -- LibreOffice / OpenOffice
UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T861873672"] = "Export Chat to Microsoft Word" UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T4258507172"] = "LibreOffice / OpenOffice"
-- Export chat
UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T72398679"] = "Export chat"
-- The selected model '{0}' is no longer available from '{1}' (provider={2}). Please adapt your provider settings. -- The selected model '{0}' is no longer available from '{1}' (provider={2}). Please adapt your provider settings.
UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTTEXT::T3267850764"] = "The selected model '{0}' is no longer available from '{1}' (provider={2}). Please adapt your provider settings." UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTTEXT::T3267850764"] = "The selected model '{0}' is no longer available from '{1}' (provider={2}). Please adapt your provider settings."
@ -8953,17 +8980,29 @@ UI_TEXT_CONTENT["AISTUDIO::TOOLS::PANDOC::T695293525"] = "AI Studio couldn't fin
-- AI Studio couldn't install Pandoc. -- AI Studio couldn't install Pandoc.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PANDOC::T932858631"] = "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. -- Error during document export
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PANDOCEXPORT::T1473115556"] = "Pandoc is required for Microsoft Word export." UI_TEXT_CONTENT["AISTUDIO::TOOLS::PANDOCEXPORT::T1606201199"] = "Error during document export"
-- Pandoc Installation -- Pandoc Installation
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PANDOCEXPORT::T185447014"] = "Pandoc Installation" UI_TEXT_CONTENT["AISTUDIO::TOOLS::PANDOCEXPORT::T185447014"] = "Pandoc Installation"
-- Error during Microsoft Word export -- Document export successful
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PANDOCEXPORT::T3290596792"] = "Error during Microsoft Word export" UI_TEXT_CONTENT["AISTUDIO::TOOLS::PANDOCEXPORT::T2074749452"] = "Document export successful"
-- Microsoft Word export successful -- Pandoc is required for document export.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PANDOCEXPORT::T4256043333"] = "Microsoft Word export successful" UI_TEXT_CONTENT["AISTUDIO::TOOLS::PANDOCEXPORT::T274234461"] = "Pandoc is required for document export."
-- Export chat
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PANDOCEXPORT::T72398679"] = "Export chat"
-- Error during document export
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLAINFILEEXPORT::T1606201199"] = "Error during document export"
-- Document export successful
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLAINFILEEXPORT::T2074749452"] = "Document export successful"
-- Export chat
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLAINFILEEXPORT::T72398679"] = "Export chat"
-- Text -- Text
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::ASSISTANTS::DATAMODEL::ASSISTANTCOMPONENTTYPEEXTENSIONS::T1041509726"] = "Text" UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::ASSISTANTS::DATAMODEL::ASSISTANTCOMPONENTTYPEEXTENSIONS::T1041509726"] = "Text"
@ -9481,6 +9520,9 @@ UI_TEXT_CONTENT["AISTUDIO::TOOLS::RUST::FILETYPES::T3543954504"] = "Certificate
-- Source like prefix -- Source like prefix
UI_TEXT_CONTENT["AISTUDIO::TOOLS::RUST::FILETYPES::T378481461"] = "Source like prefix" UI_TEXT_CONTENT["AISTUDIO::TOOLS::RUST::FILETYPES::T378481461"] = "Source like prefix"
-- Markdown
UI_TEXT_CONTENT["AISTUDIO::TOOLS::RUST::FILETYPES::T4132508012"] = "Markdown"
-- Document -- Document
UI_TEXT_CONTENT["AISTUDIO::TOOLS::RUST::FILETYPES::T4165204724"] = "Document" UI_TEXT_CONTENT["AISTUDIO::TOOLS::RUST::FILETYPES::T4165204724"] = "Document"

View File

@ -16,55 +16,84 @@
</MudText> </MudText>
</CardHeaderContent> </CardHeaderContent>
<CardHeaderActions> <CardHeaderActions>
@if (this.Content.FileAttachments.Count > 0) <div class="d-flex">
{ @if (this.Content.FileAttachments.Count > 0)
<MudTooltip Text="@T("Number of attachments")" Placement="Placement.Bottom"> {
<MudBadge Content="@this.Content.FileAttachments.Count" Color="Color.Primary" Overlap="true" BadgeClass="sources-card-header"> <MudTooltip Text="@T("Number of attachments")" Placement="Placement.Bottom">
<MudIconButton Icon="@Icons.Material.Filled.AttachFile" <MudBadge Content="@this.Content.FileAttachments.Count" Color="Color.Primary" Overlap="true" BadgeClass="sources-card-header">
OnClick="@this.OpenAttachmentsDialog"/> <MudIconButton Icon="@Icons.Material.Filled.AttachFile"
</MudBadge> OnClick="@this.OpenAttachmentsDialog"/>
</MudTooltip> </MudBadge>
} </MudTooltip>
@if (this.Content.Sources.Count > 0) }
{ @if (this.Content.Sources.Count > 0)
<MudTooltip Text="@T("Number of sources")" Placement="Placement.Bottom"> {
<MudBadge Content="@this.Content.Sources.Count" Color="Color.Primary" Overlap="true" BadgeClass="sources-card-header"> <MudTooltip Text="@T("Number of sources")" Placement="Placement.Bottom">
<MudIconButton Icon="@Icons.Material.Filled.Link"/> <MudBadge Content="@this.Content.Sources.Count" Color="Color.Primary" Overlap="true" BadgeClass="sources-card-header">
</MudBadge> <MudIconButton Icon="@Icons.Material.Filled.Link"/>
</MudTooltip> </MudBadge>
} </MudTooltip>
@if (this.IsSecondToLastBlock && this.Role is ChatRole.USER && this.EditLastUserBlockFunc is not null) }
{ @if (this.IsSecondToLastBlock && this.Role is ChatRole.USER && this.EditLastUserBlockFunc is not null)
<MudTooltip Text="@T("Edit")" Placement="Placement.Bottom"> {
<MudIconButton Icon="@Icons.Material.Filled.Edit" Color="Color.Default" OnClick="@this.EditLastUserBlock"/> <MudTooltip Text="@T("Edit")" Placement="Placement.Bottom">
</MudTooltip> <MudIconButton Icon="@Icons.Material.Filled.Edit" Color="Color.Default" OnClick="@this.EditLastUserBlock"/>
} </MudTooltip>
@if (this.IsLastContentBlock && this.Role is ChatRole.USER && this.EditLastBlockFunc is not null) }
{ @if (this.IsLastContentBlock && this.Role is ChatRole.USER && this.EditLastBlockFunc is not null)
<MudTooltip Text="@T("Edit")" Placement="Placement.Bottom"> {
<MudIconButton Icon="@Icons.Material.Filled.Edit" Color="Color.Default" OnClick="@this.EditLastBlock"/> <MudTooltip Text="@T("Edit")" Placement="Placement.Bottom">
</MudTooltip> <MudIconButton Icon="@Icons.Material.Filled.Edit" Color="Color.Default" OnClick="@this.EditLastBlock"/>
} </MudTooltip>
@if (this.IsLastContentBlock && this.Role is ChatRole.AI && this.RegenerateFunc is not null) }
{ @if (this.IsLastContentBlock && this.Role is ChatRole.AI && this.RegenerateFunc is not null)
<MudTooltip Text="@T("Regenerate")" Placement="Placement.Bottom"> {
<MudIconButton Icon="@Icons.Material.Filled.Recycling" Color="Color.Default" Disabled="@(!this.RegenerateEnabled())" OnClick="@this.RegenerateBlock"/> <MudTooltip Text="@T("Regenerate")" Placement="Placement.Bottom">
</MudTooltip> <MudIconButton Icon="@Icons.Material.Filled.Recycling" Color="Color.Default" Disabled="@(!this.RegenerateEnabled())" OnClick="@this.RegenerateBlock"/>
} </MudTooltip>
@if (this.RemoveBlockFunc is not null) }
{ @if (this.RemoveBlockFunc is not null)
<MudTooltip Text="@T("Removes this block")" Placement="Placement.Bottom"> {
<MudIconButton Icon="@Icons.Material.Filled.Delete" Color="Color.Error" OnClick="@this.RemoveBlock"/> <MudTooltip Text="@T("Removes this block")" Placement="Placement.Bottom">
</MudTooltip> <MudIconButton Icon="@Icons.Material.Filled.Delete" Color="Color.Error" OnClick="@this.RemoveBlock"/>
} </MudTooltip>
}
@if (this.Role is ChatRole.AI) @if (this.Role is ChatRole.AI)
{ {
<MudTooltip Text="@T("Export Chat to Microsoft Word")" Placement="Placement.Bottom"> <MudTooltip Text="@T("Export chat")" Placement="Placement.Bottom">
<MudIconButton Icon="@Icons.Material.Filled.Save" OnClick="@this.ExportToWord"/> <MudMenu Icon="@Icons.Material.Filled.Save">
</MudTooltip> <MudMenuItem OnClick="@(() => this.ExportDocument(FileExportFormat.MICROSOFT_WORD))" Icon="@Icons.Custom.FileFormats.FileWord">
} @T("Microsoft Word (.docx)")
<MudCopyClipboardButton Content="@this.Content" Type="@this.Type" Size="Size.Medium"/> </MudMenuItem>
<MudMenuItem OnClick="@(() => this.ExportDocument(FileExportFormat.OPEN_DOCUMENT_TEXT))" Icon="@Icons.Custom.FileFormats.FileDocument">
<MudTooltip Text="@T("LibreOffice / OpenOffice")">
@T("OpenDocument Text (.odt)")
</MudTooltip>
</MudMenuItem>
<MudMenuItem OnClick="@(() => this.ExportDocument(FileExportFormat.LATEX))" Icon="@Icons.Material.Filled.Functions">
@T("LaTeX (.tex)")
</MudMenuItem>
@if (this.HasCsv)
{
<MudDivider />
<MudMenuItem OnClick="@(() => this.ExportDocument(FileExportFormat.CSV))" Icon="@Icons.Material.Filled.TableChart">
@T("CSV (.csv)")
</MudMenuItem>
}
<MudDivider />
<MudMenuItem OnClick="@(() => this.ExportDocument(FileExportFormat.MARKDOWN))" Icon="@Icons.Material.Filled.TextFields">
@T("Markdown (.md)")
</MudMenuItem>
<MudMenuItem OnClick="@(() => this.ExportDocument(FileExportFormat.HTML))" Icon="@Icons.Material.Filled.Html">
<MudTooltip Text="@T("Webpage")">
@T("Hypertext (.html)")
</MudTooltip>
</MudMenuItem>
</MudMenu>
</MudTooltip>
}
<MudCopyClipboardButton Content="@this.Content" Type="@this.Type" Size="Size.Medium"/></div>
</CardHeaderActions> </CardHeaderActions>
</MudCardHeader> </MudCardHeader>
<MudCardContent> <MudCardContent>

View File

@ -33,6 +33,8 @@ public partial class ContentBlockComponent : MSGComponentBase, IAsyncDisposable
"<iframe", "<iframe",
"<svg", "<svg",
]; ];
private static readonly ILogger LOGGER = Program.LOGGER_FACTORY.CreateLogger(nameof(ContentBlockComponent));
/// <summary> /// <summary>
/// The role of the chat content block. /// The role of the chat content block.
@ -104,6 +106,8 @@ public partial class ContentBlockComponent : MSGComponentBase, IAsyncDisposable
private bool hasActiveMathContainer; private bool hasActiveMathContainer;
private bool isDisposed; private bool isDisposed;
private bool HasCsv => PlainFileExport.TryExtractCsvContent(this.Content, out _);
#region Overrides of ComponentBase #region Overrides of ComponentBase
protected override async Task OnInitializedAsync() protected override async Task OnInitializedAsync()
@ -546,9 +550,34 @@ public partial class ContentBlockComponent : MSGComponentBase, IAsyncDisposable
await this.RemoveBlockFunc(this.Content); await this.RemoveBlockFunc(this.Content);
} }
private async Task ExportToWord() private async Task ExportDocument(FileExportFormat format)
{ {
await PandocExport.ToMicrosoftWord(this.RustService, this.DialogService, T("Export Chat to Microsoft Word"), this.Content); try
{
switch (format)
{
case FileExportFormat.MARKDOWN:
case FileExportFormat.CSV:
await PlainFileExport.ToFile(this.RustService, format, this.Content);
break;
case FileExportFormat.MICROSOFT_WORD:
case FileExportFormat.OPEN_DOCUMENT_TEXT:
case FileExportFormat.HTML:
case FileExportFormat.LATEX:
await PandocExport.ToDocument(this.RustService, this.DialogService, format, this.Content);
break;
default:
LOGGER.LogError($"No exporter is registered for {format}.");
return;
}
}
catch (ArgumentOutOfRangeException e)
{
await this.MessageBus.SendError(new(Icons.Material.Filled.Error, string.Format(this.T("Failed to export document to unknown file format '{0}'."), format)));
LOGGER.LogError($"Failed to export document, because the format ('{format}') is unknown to our Pandoc service:\n{e}");
}
} }
private async Task RegenerateBlock() private async Task RegenerateBlock()

View File

@ -2826,6 +2826,9 @@ UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T1347427447"] = "Möchte
-- Yes, remove the AI response and edit it -- Yes, remove the AI response and edit it
UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T1350385882"] = "Ja, entferne die KI-Antwort und bearbeite sie." UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T1350385882"] = "Ja, entferne die KI-Antwort und bearbeite sie."
-- Webpage
UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T1578631058"] = "Webseite"
-- Yes, regenerate it -- Yes, regenerate it
UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T1603883875"] = "Ja, neu generieren" UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T1603883875"] = "Ja, neu generieren"
@ -2841,18 +2844,39 @@ UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T2018431076"] = "Möchte
-- Removes this block -- Removes this block
UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T2093355991"] = "Entfernt diesen Block" UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T2093355991"] = "Entfernt diesen Block"
-- LaTeX (.tex)
UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T2233607007"] = "LaTeX (.tex)"
-- OpenDocument Text (.odt)
UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T2253393351"] = "OpenDocument-Text (.odt)"
-- Regenerate Message -- Regenerate Message
UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T2308444540"] = "Nachricht neu erstellen" UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T2308444540"] = "Nachricht neu erstellen"
-- Markdown (.md)
UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T2319970170"] = "Markdown (.md)"
-- Hypertext (.html)
UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T2504486680"] = "Hypertext (.html)"
-- CSV (.csv)
UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T2853636652"] = "CSV (.csv)"
-- Number of attachments -- Number of attachments
UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T3018847255"] = "Anzahl der Anhänge" UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T3018847255"] = "Anzahl der Anhänge"
-- Microsoft Word (.docx)
UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T3054800422"] = "Microsoft Word (.docx)"
-- Cannot render content of type {0} yet. -- Cannot render content of type {0} yet.
UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T3175548294"] = "Der Inhaltstyp {0} kann noch nicht angezeigt werden." UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T3175548294"] = "Der Inhaltstyp {0} kann noch nicht angezeigt werden."
-- Edit -- Edit
UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T3267849393"] = "Bearbeiten" UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T3267849393"] = "Bearbeiten"
-- Failed to export document to unknown file format '{0}'.
UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T3566938024"] = "Export des Dokuments in das unbekannte Dateiformat „{0}“ fehlgeschlagen."
-- Regenerate -- Regenerate
UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T3587744975"] = "Neu generieren" UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T3587744975"] = "Neu generieren"
@ -2865,8 +2889,11 @@ UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T4070211974"] = "Nachric
-- No, keep it -- No, keep it
UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T4188329028"] = "Nein, behalten" UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T4188329028"] = "Nein, behalten"
-- Export Chat to Microsoft Word -- LibreOffice / OpenOffice
UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T861873672"] = "Chat in Microsoft Word exportieren" UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T4258507172"] = "LibreOffice / OpenOffice"
-- Export chat
UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T72398679"] = "Chat exportieren"
-- The selected model '{0}' is no longer available from '{1}' (provider={2}). Please adapt your provider settings. -- The selected model '{0}' is no longer available from '{1}' (provider={2}). Please adapt your provider settings.
UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTTEXT::T3267850764"] = "Das ausgewählte Modell '{0}' ist bei '{1}' (Anbieter={2}) nicht mehr verfügbar. Bitte passen Sie Ihre Anbietereinstellungen an." UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTTEXT::T3267850764"] = "Das ausgewählte Modell '{0}' ist bei '{1}' (Anbieter={2}) nicht mehr verfügbar. Bitte passen Sie Ihre Anbietereinstellungen an."
@ -8955,17 +8982,29 @@ UI_TEXT_CONTENT["AISTUDIO::TOOLS::PANDOC::T695293525"] = "AI Studio konnte die n
-- AI Studio couldn't install Pandoc. -- AI Studio couldn't install Pandoc.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PANDOC::T932858631"] = "AI Studio konnte Pandoc nicht installieren." UI_TEXT_CONTENT["AISTUDIO::TOOLS::PANDOC::T932858631"] = "AI Studio konnte Pandoc nicht installieren."
-- Pandoc is required for Microsoft Word export. -- Error during document export
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PANDOCEXPORT::T1473115556"] = "Pandoc wird für den Export nach Microsoft Word benötigt." UI_TEXT_CONTENT["AISTUDIO::TOOLS::PANDOCEXPORT::T1606201199"] = "Fehler beim Dokumentexport"
-- Pandoc Installation -- Pandoc Installation
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PANDOCEXPORT::T185447014"] = "Pandoc-Installation" UI_TEXT_CONTENT["AISTUDIO::TOOLS::PANDOCEXPORT::T185447014"] = "Pandoc-Installation"
-- Error during Microsoft Word export -- Document export successful
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PANDOCEXPORT::T3290596792"] = "Fehler beim Exportieren nach Microsoft Word" UI_TEXT_CONTENT["AISTUDIO::TOOLS::PANDOCEXPORT::T2074749452"] = "Dokumentexport erfolgreich"
-- Microsoft Word export successful -- Pandoc is required for document export.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PANDOCEXPORT::T4256043333"] = "Export nach Microsoft Word erfolgreich" UI_TEXT_CONTENT["AISTUDIO::TOOLS::PANDOCEXPORT::T274234461"] = "Pandoc wird für den Dokumentexport benötigt."
-- Export chat
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PANDOCEXPORT::T72398679"] = "Chat exportieren"
-- Error during document export
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLAINFILEEXPORT::T1606201199"] = "Fehler beim Dokumentexport"
-- Document export successful
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLAINFILEEXPORT::T2074749452"] = "Dokumentexport erfolgreich"
-- Export chat
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLAINFILEEXPORT::T72398679"] = "Chat exportieren"
-- Text -- Text
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::ASSISTANTS::DATAMODEL::ASSISTANTCOMPONENTTYPEEXTENSIONS::T1041509726"] = "Text" UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::ASSISTANTS::DATAMODEL::ASSISTANTCOMPONENTTYPEEXTENSIONS::T1041509726"] = "Text"
@ -9483,6 +9522,9 @@ UI_TEXT_CONTENT["AISTUDIO::TOOLS::RUST::FILETYPES::T3543954504"] = "Zertifikatsb
-- Source like prefix -- Source like prefix
UI_TEXT_CONTENT["AISTUDIO::TOOLS::RUST::FILETYPES::T378481461"] = "Source Code ähnlicher Prefix" UI_TEXT_CONTENT["AISTUDIO::TOOLS::RUST::FILETYPES::T378481461"] = "Source Code ähnlicher Prefix"
-- Markdown
UI_TEXT_CONTENT["AISTUDIO::TOOLS::RUST::FILETYPES::T4132508012"] = "Markdown"
-- Document -- Document
UI_TEXT_CONTENT["AISTUDIO::TOOLS::RUST::FILETYPES::T4165204724"] = "Dokument" UI_TEXT_CONTENT["AISTUDIO::TOOLS::RUST::FILETYPES::T4165204724"] = "Dokument"

View File

@ -2826,6 +2826,9 @@ UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T1347427447"] = "Do you
-- Yes, remove the AI response and edit it -- Yes, remove the AI response and edit it
UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T1350385882"] = "Yes, remove the AI response and edit it" UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T1350385882"] = "Yes, remove the AI response and edit it"
-- Webpage
UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T1578631058"] = "Webpage"
-- Yes, regenerate it -- Yes, regenerate it
UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T1603883875"] = "Yes, regenerate it" UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T1603883875"] = "Yes, regenerate it"
@ -2841,18 +2844,39 @@ UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T2018431076"] = "Do you
-- Removes this block -- Removes this block
UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T2093355991"] = "Removes this block" UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T2093355991"] = "Removes this block"
-- LaTeX (.tex)
UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T2233607007"] = "LaTeX (.tex)"
-- OpenDocument Text (.odt)
UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T2253393351"] = "OpenDocument Text (.odt)"
-- Regenerate Message -- Regenerate Message
UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T2308444540"] = "Regenerate Message" UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T2308444540"] = "Regenerate Message"
-- Markdown (.md)
UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T2319970170"] = "Markdown (.md)"
-- Hypertext (.html)
UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T2504486680"] = "Hypertext (.html)"
-- CSV (.csv)
UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T2853636652"] = "CSV (.csv)"
-- Number of attachments -- Number of attachments
UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T3018847255"] = "Number of attachments" UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T3018847255"] = "Number of attachments"
-- Microsoft Word (.docx)
UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T3054800422"] = "Microsoft Word (.docx)"
-- Cannot render content of type {0} yet. -- Cannot render content of type {0} yet.
UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T3175548294"] = "Cannot render content of type {0} yet." UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T3175548294"] = "Cannot render content of type {0} yet."
-- Edit -- Edit
UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T3267849393"] = "Edit" UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T3267849393"] = "Edit"
-- Failed to export document to unknown file format '{0}'.
UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T3566938024"] = "Failed to export document to unknown file format '{0}'."
-- Regenerate -- Regenerate
UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T3587744975"] = "Regenerate" UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T3587744975"] = "Regenerate"
@ -2865,8 +2889,11 @@ UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T4070211974"] = "Remove
-- No, keep it -- No, keep it
UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T4188329028"] = "No, keep it" UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T4188329028"] = "No, keep it"
-- Export Chat to Microsoft Word -- LibreOffice / OpenOffice
UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T861873672"] = "Export Chat to Microsoft Word" UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T4258507172"] = "LibreOffice / OpenOffice"
-- Export chat
UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T72398679"] = "Export chat"
-- The selected model '{0}' is no longer available from '{1}' (provider={2}). Please adapt your provider settings. -- The selected model '{0}' is no longer available from '{1}' (provider={2}). Please adapt your provider settings.
UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTTEXT::T3267850764"] = "The selected model '{0}' is no longer available from '{1}' (provider={2}). Please adapt your provider settings." UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTTEXT::T3267850764"] = "The selected model '{0}' is no longer available from '{1}' (provider={2}). Please adapt your provider settings."
@ -8955,17 +8982,29 @@ UI_TEXT_CONTENT["AISTUDIO::TOOLS::PANDOC::T695293525"] = "AI Studio couldn't fin
-- AI Studio couldn't install Pandoc. -- AI Studio couldn't install Pandoc.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PANDOC::T932858631"] = "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. -- Error during document export
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PANDOCEXPORT::T1473115556"] = "Pandoc is required for Microsoft Word export." UI_TEXT_CONTENT["AISTUDIO::TOOLS::PANDOCEXPORT::T1606201199"] = "Error during document export"
-- Pandoc Installation -- Pandoc Installation
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PANDOCEXPORT::T185447014"] = "Pandoc Installation" UI_TEXT_CONTENT["AISTUDIO::TOOLS::PANDOCEXPORT::T185447014"] = "Pandoc Installation"
-- Error during Microsoft Word export -- Document export successful
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PANDOCEXPORT::T3290596792"] = "Error during Microsoft Word export" UI_TEXT_CONTENT["AISTUDIO::TOOLS::PANDOCEXPORT::T2074749452"] = "Document export successful"
-- Microsoft Word export successful -- Pandoc is required for document export.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PANDOCEXPORT::T4256043333"] = "Microsoft Word export successful" UI_TEXT_CONTENT["AISTUDIO::TOOLS::PANDOCEXPORT::T274234461"] = "Pandoc is required for document export."
-- Export chat
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PANDOCEXPORT::T72398679"] = "Export chat"
-- Error during document export
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLAINFILEEXPORT::T1606201199"] = "Error during document export"
-- Document export successful
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLAINFILEEXPORT::T2074749452"] = "Document export successful"
-- Export chat
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLAINFILEEXPORT::T72398679"] = "Export chat"
-- Text -- Text
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::ASSISTANTS::DATAMODEL::ASSISTANTCOMPONENTTYPEEXTENSIONS::T1041509726"] = "Text" UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::ASSISTANTS::DATAMODEL::ASSISTANTCOMPONENTTYPEEXTENSIONS::T1041509726"] = "Text"
@ -9483,6 +9522,9 @@ UI_TEXT_CONTENT["AISTUDIO::TOOLS::RUST::FILETYPES::T3543954504"] = "Certificate
-- Source like prefix -- Source like prefix
UI_TEXT_CONTENT["AISTUDIO::TOOLS::RUST::FILETYPES::T378481461"] = "Source like prefix" UI_TEXT_CONTENT["AISTUDIO::TOOLS::RUST::FILETYPES::T378481461"] = "Source like prefix"
-- Markdown
UI_TEXT_CONTENT["AISTUDIO::TOOLS::RUST::FILETYPES::T4132508012"] = "Markdown"
-- Document -- Document
UI_TEXT_CONTENT["AISTUDIO::TOOLS::RUST::FILETYPES::T4165204724"] = "Document" UI_TEXT_CONTENT["AISTUDIO::TOOLS::RUST::FILETYPES::T4165204724"] = "Document"

View File

@ -0,0 +1,11 @@
namespace AIStudio.Tools;
public enum FileExportFormat
{
MICROSOFT_WORD,
OPEN_DOCUMENT_TEXT,
HTML,
MARKDOWN,
CSV,
LATEX,
}

View File

@ -4,7 +4,6 @@ using AIStudio.Dialogs;
using AIStudio.Tools.PluginSystem; using AIStudio.Tools.PluginSystem;
using AIStudio.Tools.Rust; using AIStudio.Tools.Rust;
using AIStudio.Tools.Services; using AIStudio.Tools.Services;
using DialogOptions = AIStudio.Dialogs.DialogOptions; using DialogOptions = AIStudio.Dialogs.DialogOptions;
namespace AIStudio.Tools; namespace AIStudio.Tools;
@ -12,19 +11,34 @@ namespace AIStudio.Tools;
public static class PandocExport public static class PandocExport
{ {
private static readonly ILogger LOGGER = Program.LOGGER_FACTORY.CreateLogger(nameof(PandocExport)); private static readonly ILogger LOGGER = Program.LOGGER_FACTORY.CreateLogger(nameof(PandocExport));
private sealed record ExportTarget(string DisplayName, string PandocOutputFormat, FileTypeFilter FileType);
private static readonly ExportTarget MICROSOFT_WORD = new("Microsoft Word (.docx)", "docx", FileTypes.MS_WORD);
private static readonly ExportTarget OPEN_DOCUMENT_TEXT = new("OpenDocument Text (.odt)", "odt", FileTypes.OPEN_DOCUMENT_TEXT);
private static readonly ExportTarget HTML = new("Hypertext (.html)", "html", FileTypes.HYPERTEXT);
private static readonly ExportTarget LATEX = new("LaTeX (.tex)", "latex", FileTypes.LATEX);
private static string TB(string fallbackEn) => I18N.I.T(fallbackEn, typeof(PandocExport).Namespace, nameof(PandocExport)); private static string TB(string fallbackEn) => I18N.I.T(fallbackEn, typeof(PandocExport).Namespace, nameof(PandocExport));
public static async Task<bool> ToMicrosoftWord(RustService rustService, IDialogService dialogService, string dialogTitle, IContent markdownContent) public static async Task<bool> ToDocument(RustService rustService, IDialogService dialogService, FileExportFormat format, IContent markdownContent)
{ {
var response = await rustService.SaveFile(dialogTitle, [FileTypes.MS_WORD]); var exportTarget = format switch
{
FileExportFormat.MICROSOFT_WORD => MICROSOFT_WORD,
FileExportFormat.OPEN_DOCUMENT_TEXT => OPEN_DOCUMENT_TEXT,
FileExportFormat.HTML => HTML,
FileExportFormat.LATEX => LATEX,
_ => throw new ArgumentOutOfRangeException(nameof(format), format, null),
};
var response = await rustService.SaveFile(TB("Export chat"), [exportTarget.FileType]);
if (response.UserCancelled) if (response.UserCancelled)
{ {
LOGGER.LogInformation("User cancelled the save dialog."); LOGGER.LogInformation("User cancelled the save dialog.");
return false; return false;
} }
LOGGER.LogInformation($"The user chose the path '{response.SaveFilePath}' for the Microsoft Word export."); LOGGER.LogInformation("The user chose the path '{SaveFilePath}' for the {ExportFormat} export.", response.SaveFilePath, exportTarget.DisplayName);
var tempMarkdownFilePath = string.Empty; var tempMarkdownFilePath = string.Empty;
try try
@ -36,9 +50,9 @@ public static class PandocExport
var markdownText = markdownContent switch var markdownText = markdownContent switch
{ {
ContentText text => text.Text, ContentText text => text.Text,
ContentImage _ => "Image export to Microsoft Word not yet possible", ContentImage _ => "Image export is not yet possible.",
_ => "Unknown content type. Cannot export to Word." _ => "Unknown content type. Cannot export document."
}; };
// Write text content to a temporary file: // Write text content to a temporary file:
@ -60,17 +74,17 @@ public static class PandocExport
if (!pandocState.IsAvailable) if (!pandocState.IsAvailable)
{ {
LOGGER.LogError("Pandoc is not available after installation attempt."); LOGGER.LogError("Pandoc is not available after installation attempt.");
await MessageBus.INSTANCE.SendError(new(Icons.Material.Filled.Cancel, TB("Pandoc is required for Microsoft Word export."))); await MessageBus.INSTANCE.SendError(new(Icons.Material.Filled.Cancel, TB("Pandoc is required for document export.")));
return false; return false;
} }
} }
// Call Pandoc to create the Word file: // Call Pandoc to create the document:
var pandoc = await PandocProcessBuilder var pandoc = await PandocProcessBuilder
.Create() .Create()
.UseStandaloneMode() .UseStandaloneMode()
.WithInputFormat("gfm+emoji+tex_math_dollars") .WithInputFormat("gfm+emoji+tex_math_dollars")
.WithOutputFormat("docx") .WithOutputFormat(exportTarget.PandocOutputFormat)
.WithOutputFile(response.SaveFilePath) .WithOutputFile(response.SaveFilePath)
.WithInputFile(tempMarkdownFilePath) .WithInputFile(tempMarkdownFilePath)
.BuildAsync(rustService); .BuildAsync(rustService);
@ -94,19 +108,19 @@ public static class PandocExport
if (process.ExitCode is not 0) if (process.ExitCode is not 0)
{ {
LOGGER.LogError("Pandoc failed with exit code {ProcessExitCode}: '{ErrorText}'", process.ExitCode, error); LOGGER.LogError("Pandoc failed with exit code {ProcessExitCode}: '{ErrorText}'", process.ExitCode, error);
await MessageBus.INSTANCE.SendError(new(Icons.Material.Filled.Cancel, TB("Error during Microsoft Word export"))); await MessageBus.INSTANCE.SendError(new(Icons.Material.Filled.Cancel, TB("Error during document export")));
return false; return false;
} }
LOGGER.LogInformation("Pandoc conversion successful."); LOGGER.LogInformation("Pandoc conversion successful.");
await MessageBus.INSTANCE.SendSuccess(new(Icons.Material.Filled.CheckCircle, TB("Microsoft Word export successful"))); await MessageBus.INSTANCE.SendSuccess(new(Icons.Material.Filled.CheckCircle, TB("Document export successful")));
return true; return true;
} }
catch (Exception ex) catch (Exception ex)
{ {
LOGGER.LogError(ex, "Error during Word export."); LOGGER.LogError(ex, "Error during {ExportFormat} export.", exportTarget.DisplayName);
await MessageBus.INSTANCE.SendError(new(Icons.Material.Filled.Cancel, TB("Error during Microsoft Word export"))); await MessageBus.INSTANCE.SendError(new(Icons.Material.Filled.Cancel, TB("Error during document export")));
return false; return false;
} }
finally finally

View File

@ -0,0 +1,103 @@
using System.Text.RegularExpressions;
using AIStudio.Chat;
using AIStudio.Tools.PluginSystem;
using AIStudio.Tools.Rust;
using AIStudio.Tools.Services;
namespace AIStudio.Tools;
public static partial class PlainFileExport
{
private static readonly ILogger LOGGER = Program.LOGGER_FACTORY.CreateLogger(nameof(PlainFileExport));
private sealed record ExportTarget(string DisplayName, FileTypeFilter FileType);
private static readonly ExportTarget MARKDOWN = new("Markdown (.md)", FileTypes.MARKDOWN);
private static readonly ExportTarget CSV = new("CSV (.csv)", FileTypes.CSV);
private static string TB(string fallbackEn) => I18N.I.T(fallbackEn, typeof(PlainFileExport).Namespace, nameof(PlainFileExport));
/// <summary>
/// Extracts the content of the first complete Markdown code block marked as CSV.
/// </summary>
public static bool TryExtractCsvContent(IContent content, out string csvContent)
{
if (content is ContentText text)
{
var match = CsvCodeFenceRegex().Match(text.Text);
if (match.Success)
{
csvContent = match.Groups["content"].Value;
return true;
}
}
csvContent = string.Empty;
return false;
}
[GeneratedRegex(
"""
# Matches an opening Markdown code fence and captures its delimiter.
^(?<fence>`{3,}|~{3,})
# Matches the csv language identifier and the end of the opening fence line.
# Also allow tab-separated, pipe-separated and semicolon separated values at the code fence
[ \t]*(csv|tsv|psv|ssv)[ \t]*\r?\n
# Captures the content of the first matching fenced code block.
(?<content>[\s\S]*?)
# Matches the corresponding closing fence followed by a line ending or end of input.
^\k<fence>[ \t]*(?=\r?\n|$)
""",
RegexOptions.IgnoreCase |
RegexOptions.Multiline |
RegexOptions.IgnorePatternWhitespace)]
private static partial Regex CsvCodeFenceRegex();
public static async Task<bool> ToFile(RustService rustService, FileExportFormat format, IContent markdownContent)
{
var exportTarget = format switch
{
FileExportFormat.MARKDOWN => MARKDOWN,
FileExportFormat.CSV => CSV,
_ => throw new ArgumentOutOfRangeException(nameof(format), format, null),
};
var response = await rustService.SaveFile(TB("Export chat"), [exportTarget.FileType]);
if (response.UserCancelled)
{
LOGGER.LogInformation("User cancelled the save dialog.");
return false;
}
LOGGER.LogInformation($"The user chose the path '{response.SaveFilePath}' for the {exportTarget.DisplayName} export.");
try
{
var fileContent = format switch
{
FileExportFormat.MARKDOWN => markdownContent switch
{
ContentText text => text.Text,
ContentImage _ => "Image export is not yet possible.",
_ => "Unknown content type. Cannot export document."
},
FileExportFormat.CSV when TryExtractCsvContent(markdownContent, out var csvContent) => csvContent,
_ => throw new ArgumentOutOfRangeException(nameof(format), format, null),
};
await File.WriteAllTextAsync(response.SaveFilePath, fileContent);
await MessageBus.INSTANCE.SendSuccess(new(Icons.Material.Filled.CheckCircle, TB("Document export successful")));
return true;
}
catch (Exception ex)
{
LOGGER.LogError(ex, $"Error during {exportTarget.DisplayName} export.");
await MessageBus.INSTANCE.SendError(new(Icons.Material.Filled.Cancel, TB("Error during document export")));
return false;
}
}
}

View File

@ -37,6 +37,7 @@ public static class FileTypes
/// Gets the standalone HTML filter used for visual briefing import and export. /// Gets the standalone HTML filter used for visual briefing import and export.
/// </summary> /// </summary>
public static readonly FileTypeFilter VISUAL_BRIEFING_HTML = FileTypeFilter.Leaf(TB("Visual briefing"), "html"); public static readonly FileTypeFilter VISUAL_BRIEFING_HTML = FileTypeFilter.Leaf(TB("Visual briefing"), "html");
public static readonly FileTypeFilter HYPERTEXT = FileTypeFilter.Leaf("HTML", "html");
public static readonly FileTypeFilter APP = FileTypeFilter.Leaf("Swift/Kotlin", "swift", "kt"); public static readonly FileTypeFilter APP = FileTypeFilter.Leaf("Swift/Kotlin", "swift", "kt");
public static readonly FileTypeFilter SHELL = FileTypeFilter.Leaf("Shell", "sh", "bash", "zsh"); public static readonly FileTypeFilter SHELL = FileTypeFilter.Leaf("Shell", "sh", "bash", "zsh");
public static readonly FileTypeFilter LOG = FileTypeFilter.Leaf("Log", "log"); public static readonly FileTypeFilter LOG = FileTypeFilter.Leaf("Log", "log");
@ -51,17 +52,21 @@ public static class FileTypes
// Document hierarchy // Document hierarchy
public static readonly FileTypeFilter PDF = FileTypeFilter.Leaf("PDF", "pdf"); public static readonly FileTypeFilter PDF = FileTypeFilter.Leaf("PDF", "pdf");
public static readonly FileTypeFilter TEXT = FileTypeFilter.Leaf(TB("Text"), "txt", "md", "rtf"); public static readonly FileTypeFilter TEXT = FileTypeFilter.Leaf(TB("Text"), "txt", "md", "rtf");
public static readonly FileTypeFilter MARKDOWN = FileTypeFilter.Leaf(TB("Markdown"), "md");
public static readonly FileTypeFilter CSV = FileTypeFilter.Leaf("CSV", "csv");
public static readonly FileTypeFilter MS_WORD = FileTypeFilter.Leaf("Microsoft Word", "docx"); public static readonly FileTypeFilter MS_WORD = FileTypeFilter.Leaf("Microsoft Word", "docx");
public static readonly FileTypeFilter WORD = FileTypeFilter.Composite("Word", ["odt"], MS_WORD); public static readonly FileTypeFilter OPEN_DOCUMENT_TEXT = FileTypeFilter.Leaf("OpenDocument Text", "odt");
public static readonly FileTypeFilter WORD = FileTypeFilter.Parent("Word", OPEN_DOCUMENT_TEXT, MS_WORD);
public static readonly FileTypeFilter EXCEL = FileTypeFilter.Leaf("Excel", "xls", "xlsx"); public static readonly FileTypeFilter EXCEL = FileTypeFilter.Leaf("Excel", "xls", "xlsx");
public static readonly FileTypeFilter POWER_POINT = FileTypeFilter.Leaf("PowerPoint", "ppt", "pptx", "odp"); public static readonly FileTypeFilter POWER_POINT = FileTypeFilter.Leaf("PowerPoint", "ppt", "pptx", "odp");
public static readonly FileTypeFilter MAIL = FileTypeFilter.Leaf(TB("Mail"), "eml", "msg", "mbox"); public static readonly FileTypeFilter MAIL = FileTypeFilter.Leaf(TB("Mail"), "eml", "msg", "mbox");
public static readonly FileTypeFilter LATEX = FileTypeFilter.Leaf("LaTeX", "tex", "bib", "sty", "cls", "log"); public static readonly FileTypeFilter LATEX_FAMILY = FileTypeFilter.Leaf("LaTeX", "tex", "bib", "sty", "cls", "log");
public static readonly FileTypeFilter LATEX = FileTypeFilter.Leaf("LaTeX", "tex");
public static readonly FileTypeFilter OFFICE_FILES = FileTypeFilter.Parent(TB("Office Files"), public static readonly FileTypeFilter OFFICE_FILES = FileTypeFilter.Parent(TB("Office Files"),
WORD, EXCEL, POWER_POINT, PDF); WORD, EXCEL, POWER_POINT, PDF);
public static readonly FileTypeFilter DOCUMENT = FileTypeFilter.Parent(TB("Document"), public static readonly FileTypeFilter DOCUMENT = FileTypeFilter.Parent(TB("Document"),
TEXT, OFFICE_FILES, SOURCE_CODE, LATEX); TEXT, OFFICE_FILES, SOURCE_CODE, LATEX_FAMILY);
// Media hierarchy // Media hierarchy
public static readonly FileTypeFilter IMAGE = FileTypeFilter.Leaf(TB("Image"), public static readonly FileTypeFilter IMAGE = FileTypeFilter.Leaf(TB("Image"),