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
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
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
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
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
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.
UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T3175548294"] = "Cannot render content of type {0} yet."
-- 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
UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T3587744975"] = "Regenerate"
@ -2863,8 +2887,11 @@ 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"
-- LibreOffice / OpenOffice
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.
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.
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."
-- Error during document export
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PANDOCEXPORT::T1606201199"] = "Error during document export"
-- Pandoc Installation
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PANDOCEXPORT::T185447014"] = "Pandoc Installation"
-- Error during Microsoft Word export
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PANDOCEXPORT::T3290596792"] = "Error during Microsoft Word export"
-- Document export successful
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PANDOCEXPORT::T2074749452"] = "Document export successful"
-- Microsoft Word export successful
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PANDOCEXPORT::T4256043333"] = "Microsoft Word export successful"
-- Pandoc is required for document export.
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
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
UI_TEXT_CONTENT["AISTUDIO::TOOLS::RUST::FILETYPES::T378481461"] = "Source like prefix"
-- Markdown
UI_TEXT_CONTENT["AISTUDIO::TOOLS::RUST::FILETYPES::T4132508012"] = "Markdown"
-- Document
UI_TEXT_CONTENT["AISTUDIO::TOOLS::RUST::FILETYPES::T4165204724"] = "Document"

View File

@ -16,55 +16,84 @@
</MudText>
</CardHeaderContent>
<CardHeaderActions>
@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">
<MudIconButton Icon="@Icons.Material.Filled.AttachFile"
OnClick="@this.OpenAttachmentsDialog"/>
</MudBadge>
</MudTooltip>
}
@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">
<MudIconButton Icon="@Icons.Material.Filled.Link"/>
</MudBadge>
</MudTooltip>
}
@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>
}
@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>
}
@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>
}
@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>
}
<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">
<MudIconButton Icon="@Icons.Material.Filled.AttachFile"
OnClick="@this.OpenAttachmentsDialog"/>
</MudBadge>
</MudTooltip>
}
@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">
<MudIconButton Icon="@Icons.Material.Filled.Link"/>
</MudBadge>
</MudTooltip>
}
@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>
}
@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>
}
@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>
}
@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>
}
@if (this.Role is ChatRole.AI)
{
<MudTooltip Text="@T("Export Chat to Microsoft Word")" Placement="Placement.Bottom">
<MudIconButton Icon="@Icons.Material.Filled.Save" OnClick="@this.ExportToWord"/>
</MudTooltip>
}
<MudCopyClipboardButton Content="@this.Content" Type="@this.Type" Size="Size.Medium"/>
@if (this.Role is ChatRole.AI)
{
<MudTooltip Text="@T("Export chat")" Placement="Placement.Bottom">
<MudMenu Icon="@Icons.Material.Filled.Save">
<MudMenuItem OnClick="@(() => this.ExportDocument(FileExportFormat.MICROSOFT_WORD))" Icon="@Icons.Custom.FileFormats.FileWord">
@T("Microsoft Word (.docx)")
</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>
</MudCardHeader>
<MudCardContent>

View File

@ -33,6 +33,8 @@ public partial class ContentBlockComponent : MSGComponentBase, IAsyncDisposable
"<iframe",
"<svg",
];
private static readonly ILogger LOGGER = Program.LOGGER_FACTORY.CreateLogger(nameof(ContentBlockComponent));
/// <summary>
/// The role of the chat content block.
@ -104,6 +106,8 @@ public partial class ContentBlockComponent : MSGComponentBase, IAsyncDisposable
private bool hasActiveMathContainer;
private bool isDisposed;
private bool HasCsv => PlainFileExport.TryExtractCsvContent(this.Content, out _);
#region Overrides of ComponentBase
protected override async Task OnInitializedAsync()
@ -546,9 +550,34 @@ public partial class ContentBlockComponent : MSGComponentBase, IAsyncDisposable
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()

View File

@ -2826,6 +2826,9 @@ UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T1347427447"] = "Möchte
-- Yes, remove the AI response and edit it
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
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
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
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
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.
UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T3175548294"] = "Der Inhaltstyp {0} kann noch nicht angezeigt werden."
-- Edit
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
UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T3587744975"] = "Neu generieren"
@ -2865,8 +2889,11 @@ UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T4070211974"] = "Nachric
-- No, keep it
UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T4188329028"] = "Nein, behalten"
-- Export Chat to Microsoft Word
UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T861873672"] = "Chat in Microsoft Word exportieren"
-- LibreOffice / OpenOffice
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.
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.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PANDOC::T932858631"] = "AI Studio konnte Pandoc nicht installieren."
-- Pandoc is required for Microsoft Word export.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PANDOCEXPORT::T1473115556"] = "Pandoc wird für den Export nach Microsoft Word benötigt."
-- Error during document export
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PANDOCEXPORT::T1606201199"] = "Fehler beim Dokumentexport"
-- Pandoc Installation
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PANDOCEXPORT::T185447014"] = "Pandoc-Installation"
-- Error during Microsoft Word export
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PANDOCEXPORT::T3290596792"] = "Fehler beim Exportieren nach Microsoft Word"
-- Document export successful
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PANDOCEXPORT::T2074749452"] = "Dokumentexport erfolgreich"
-- Microsoft Word export successful
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PANDOCEXPORT::T4256043333"] = "Export nach Microsoft Word erfolgreich"
-- Pandoc is required for document export.
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
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
UI_TEXT_CONTENT["AISTUDIO::TOOLS::RUST::FILETYPES::T378481461"] = "Source Code ähnlicher Prefix"
-- Markdown
UI_TEXT_CONTENT["AISTUDIO::TOOLS::RUST::FILETYPES::T4132508012"] = "Markdown"
-- Document
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
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
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
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
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
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.
UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T3175548294"] = "Cannot render content of type {0} yet."
-- 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
UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T3587744975"] = "Regenerate"
@ -2865,8 +2889,11 @@ 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"
-- LibreOffice / OpenOffice
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.
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.
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."
-- Error during document export
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PANDOCEXPORT::T1606201199"] = "Error during document export"
-- Pandoc Installation
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PANDOCEXPORT::T185447014"] = "Pandoc Installation"
-- Error during Microsoft Word export
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PANDOCEXPORT::T3290596792"] = "Error during Microsoft Word export"
-- Document export successful
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PANDOCEXPORT::T2074749452"] = "Document export successful"
-- Microsoft Word export successful
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PANDOCEXPORT::T4256043333"] = "Microsoft Word export successful"
-- Pandoc is required for document export.
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
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
UI_TEXT_CONTENT["AISTUDIO::TOOLS::RUST::FILETYPES::T378481461"] = "Source like prefix"
-- Markdown
UI_TEXT_CONTENT["AISTUDIO::TOOLS::RUST::FILETYPES::T4132508012"] = "Markdown"
-- 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.Rust;
using AIStudio.Tools.Services;
using DialogOptions = AIStudio.Dialogs.DialogOptions;
namespace AIStudio.Tools;
@ -12,19 +11,34 @@ namespace AIStudio.Tools;
public static class 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));
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)
{
LOGGER.LogInformation("User cancelled the save dialog.");
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;
try
@ -36,9 +50,9 @@ public static class PandocExport
var markdownText = markdownContent switch
{
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:
@ -60,17 +74,17 @@ public static class PandocExport
if (!pandocState.IsAvailable)
{
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;
}
}
// Call Pandoc to create the Word file:
// Call Pandoc to create the document:
var pandoc = await PandocProcessBuilder
.Create()
.UseStandaloneMode()
.WithInputFormat("gfm+emoji+tex_math_dollars")
.WithOutputFormat("docx")
.WithOutputFormat(exportTarget.PandocOutputFormat)
.WithOutputFile(response.SaveFilePath)
.WithInputFile(tempMarkdownFilePath)
.BuildAsync(rustService);
@ -94,19 +108,19 @@ public static class PandocExport
if (process.ExitCode is not 0)
{
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;
}
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;
}
catch (Exception ex)
{
LOGGER.LogError(ex, "Error during Word export.");
await MessageBus.INSTANCE.SendError(new(Icons.Material.Filled.Cancel, TB("Error during Microsoft Word export")));
LOGGER.LogError(ex, "Error during {ExportFormat} export.", exportTarget.DisplayName);
await MessageBus.INSTANCE.SendError(new(Icons.Material.Filled.Cancel, TB("Error during document export")));
return false;
}
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.
/// </summary>
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 SHELL = FileTypeFilter.Leaf("Shell", "sh", "bash", "zsh");
public static readonly FileTypeFilter LOG = FileTypeFilter.Leaf("Log", "log");
@ -51,17 +52,21 @@ public static class FileTypes
// Document hierarchy
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 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 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 POWER_POINT = FileTypeFilter.Leaf("PowerPoint", "ppt", "pptx", "odp");
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"),
WORD, EXCEL, POWER_POINT, PDF);
public static readonly FileTypeFilter DOCUMENT = FileTypeFilter.Parent(TB("Document"),
TEXT, OFFICE_FILES, SOURCE_CODE, LATEX);
TEXT, OFFICE_FILES, SOURCE_CODE, LATEX_FAMILY);
// Media hierarchy
public static readonly FileTypeFilter IMAGE = FileTypeFilter.Leaf(TB("Image"),