added possibility to open log file in file manager

This commit is contained in:
Paul Koudelka 2026-07-15 14:05:16 +02:00
parent 11dd357a05
commit d72b7c558e
11 changed files with 418 additions and 2 deletions

View File

@ -1648,6 +1648,9 @@ UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::LOGVIEWER::ASSISTANTLOGVIEWER::T174782740
-- The log file does not exist: {0} -- The log file does not exist: {0}
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::LOGVIEWER::ASSISTANTLOGVIEWER::T1807514273"] = "The log file does not exist: {0}" UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::LOGVIEWER::ASSISTANTLOGVIEWER::T1807514273"] = "The log file does not exist: {0}"
-- Could not open the log file location.
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::LOGVIEWER::ASSISTANTLOGVIEWER::T1828231197"] = "Could not open the log file location."
-- Other -- Other
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::LOGVIEWER::ASSISTANTLOGVIEWER::T1849229205"] = "Other" UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::LOGVIEWER::ASSISTANTLOGVIEWER::T1849229205"] = "Other"
@ -1666,6 +1669,9 @@ UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::LOGVIEWER::ASSISTANTLOGVIEWER::T237835357
-- No matching log lines. -- No matching log lines.
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::LOGVIEWER::ASSISTANTLOGVIEWER::T2511997530"] = "No matching log lines." UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::LOGVIEWER::ASSISTANTLOGVIEWER::T2511997530"] = "No matching log lines."
-- Could not open the log file location: {0}
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::LOGVIEWER::ASSISTANTLOGVIEWER::T2533784927"] = "Could not open the log file location: {0}"
-- Source details -- Source details
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::LOGVIEWER::ASSISTANTLOGVIEWER::T2686813966"] = "Source details" UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::LOGVIEWER::ASSISTANTLOGVIEWER::T2686813966"] = "Source details"
@ -1678,6 +1684,9 @@ UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::LOGVIEWER::ASSISTANTLOGVIEWER::T315262563
-- Loading log file... -- Loading log file...
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::LOGVIEWER::ASSISTANTLOGVIEWER::T333036481"] = "Loading log file..." UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::LOGVIEWER::ASSISTANTLOGVIEWER::T333036481"] = "Loading log file..."
-- Unknown error
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::LOGVIEWER::ASSISTANTLOGVIEWER::T3461425987"] = "Unknown error"
-- The log file path is not available yet. -- The log file path is not available yet.
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::LOGVIEWER::ASSISTANTLOGVIEWER::T3686775689"] = "The log file path is not available yet." UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::LOGVIEWER::ASSISTANTLOGVIEWER::T3686775689"] = "The log file path is not available yet."
@ -1696,12 +1705,18 @@ UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::LOGVIEWER::ASSISTANTLOGVIEWER::T397479987
-- Usage log -- Usage log
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::LOGVIEWER::ASSISTANTLOGVIEWER::T4031747274"] = "Usage log" UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::LOGVIEWER::ASSISTANTLOGVIEWER::T4031747274"] = "Usage log"
-- Open in folder
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::LOGVIEWER::ASSISTANTLOGVIEWER::T4048746540"] = "Open in folder"
-- Log file -- Log file
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::LOGVIEWER::ASSISTANTLOGVIEWER::T4129341821"] = "Log file" UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::LOGVIEWER::ASSISTANTLOGVIEWER::T4129341821"] = "Log file"
-- Log Viewer -- Log Viewer
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::LOGVIEWER::ASSISTANTLOGVIEWER::T4130241777"] = "Log Viewer" UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::LOGVIEWER::ASSISTANTLOGVIEWER::T4130241777"] = "Log Viewer"
-- Opened the log file location.
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::LOGVIEWER::ASSISTANTLOGVIEWER::T4162897654"] = "Opened the log file location."
-- Show timestamps -- Show timestamps
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::LOGVIEWER::ASSISTANTLOGVIEWER::T469116133"] = "Show timestamps" UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::LOGVIEWER::ASSISTANTLOGVIEWER::T469116133"] = "Show timestamps"
@ -8584,24 +8599,42 @@ UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::RUSTSERVICE::T1110203516"] = "Failed
-- Failed to store the API key due to an API issue. -- Failed to store the API key due to an API issue.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::RUSTSERVICE::T1704298921"] = "Failed to store the API key due to an API issue." UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::RUSTSERVICE::T1704298921"] = "Failed to store the API key due to an API issue."
-- The runtime file manager endpoint returned '{0}'.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::RUSTSERVICE::T2158262203"] = "The runtime file manager endpoint returned '{0}'."
-- Failed to delete the secret data due to an API issue. -- Failed to delete the secret data due to an API issue.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::RUSTSERVICE::T2303057928"] = "Failed to delete the secret data due to an API issue." UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::RUSTSERVICE::T2303057928"] = "Failed to delete the secret data due to an API issue."
-- The runtime file manager endpoint is not available.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::RUSTSERVICE::T2486847754"] = "The runtime file manager endpoint is not available."
-- The runtime file manager endpoint failed without details.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::RUSTSERVICE::T3082220817"] = "The runtime file manager endpoint failed without details."
-- Successfully copied the text to your clipboard -- Successfully copied the text to your clipboard
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::RUSTSERVICE::T3351807428"] = "Successfully copied the text to your clipboard" UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::RUSTSERVICE::T3351807428"] = "Successfully copied the text to your clipboard"
-- The path does not exist and its parent folder could not be found.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::RUSTSERVICE::T3606079336"] = "The path does not exist and its parent folder could not be found."
-- Failed to delete the API key due to an API issue. -- Failed to delete the API key due to an API issue.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::RUSTSERVICE::T3658273365"] = "Failed to delete the API key due to an API issue." UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::RUSTSERVICE::T3658273365"] = "Failed to delete the API key due to an API issue."
-- Failed to copy the text to your clipboard. -- Failed to copy the text to your clipboard.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::RUSTSERVICE::T3724548108"] = "Failed to copy the text to your clipboard." UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::RUSTSERVICE::T3724548108"] = "Failed to copy the text to your clipboard."
-- The local file manager command failed: {0}
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::RUSTSERVICE::T381756374"] = "The local file manager command failed: {0}"
-- Failed to get the API key due to an API issue. -- Failed to get the API key due to an API issue.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::RUSTSERVICE::T3875720022"] = "Failed to get the API key due to an API issue." UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::RUSTSERVICE::T3875720022"] = "Failed to get the API key due to an API issue."
-- Failed to get the secret data due to an API issue. -- Failed to get the secret data due to an API issue.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::RUSTSERVICE::T4007657575"] = "Failed to get the secret data due to an API issue." UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::RUSTSERVICE::T4007657575"] = "Failed to get the secret data due to an API issue."
-- The local file manager command did not start.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::RUSTSERVICE::T613631541"] = "The local file manager command did not start."
-- No update found. -- No update found.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::UPDATESERVICE::T1015418291"] = "No update found." UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::UPDATESERVICE::T1015418291"] = "No update found."

View File

@ -22,6 +22,9 @@
</MudButton> </MudButton>
} }
<MudNumericField T="int" Value="@this.maxLines" ValueChanged="@this.MaxLinesChanged" Label="@T("Max lines")" Variant="Variant.Outlined" Margin="Margin.Dense" Min="@MIN_MAX_LINES" Max="@MAX_MAX_LINES" Step="500" Class="log-viewer-number" /> <MudNumericField T="int" Value="@this.maxLines" ValueChanged="@this.MaxLinesChanged" Label="@T("Max lines")" Variant="Variant.Outlined" Margin="Margin.Dense" Min="@MIN_MAX_LINES" Max="@MAX_MAX_LINES" Step="500" Class="log-viewer-number" />
<MudButton Variant="Variant.Outlined" Color="Color.Default" StartIcon="@Icons.Material.Filled.FolderOpen" OnClick="@this.OpenCurrentLogInFileManager" Disabled="@(!this.CanOpenCurrentLogPath)">
@T("Open in folder")
</MudButton>
</MudStack> </MudStack>
<MudStack Row="@true" Wrap="@Wrap.Wrap" AlignItems="@AlignItems.Center" Spacing="2" Class="mb-1"> <MudStack Row="@true" Wrap="@Wrap.Wrap" AlignItems="@AlignItems.Center" Spacing="2" Class="mb-1">

View File

@ -34,6 +34,9 @@ public partial class AssistantLogViewer : MSGComponentBase
[Inject] [Inject]
private RustService RustService { get; init; } = null!; private RustService RustService { get; init; } = null!;
[Inject]
private ISnackbar Snackbar { get; init; } = null!;
[Inject] [Inject]
private NavigationManager NavigationManager { get; init; } = null!; private NavigationManager NavigationManager { get; init; } = null!;
@ -65,6 +68,8 @@ public partial class AssistantLogViewer : MSGComponentBase
private string CurrentLogPath => this.selectedLogFile is LogFileKind.APP ? this.logPaths.LogAppPath : this.logPaths.LogStartupPath; private string CurrentLogPath => this.selectedLogFile is LogFileKind.APP ? this.logPaths.LogAppPath : this.logPaths.LogStartupPath;
private bool CanOpenCurrentLogPath => !string.IsNullOrWhiteSpace(this.CurrentLogPath);
private bool HasDropdownFilter => this.selectedLogLevels.Count > 0 || this.selectedLoggers.Count > 0 || this.selectedSourceDetails.Count > 0; private bool HasDropdownFilter => this.selectedLogLevels.Count > 0 || this.selectedLoggers.Count > 0 || this.selectedSourceDetails.Count > 0;
private bool HasActiveFilter => !string.IsNullOrWhiteSpace(this.filterText) || this.HasDropdownFilter; private bool HasActiveFilter => !string.IsNullOrWhiteSpace(this.filterText) || this.HasDropdownFilter;
@ -203,6 +208,68 @@ public partial class AssistantLogViewer : MSGComponentBase
await this.RefreshLogAsync(); await this.RefreshLogAsync();
} }
private async Task OpenCurrentLogInFileManager()
{
try
{
this.logPaths = await this.RustService.GetLogPaths();
}
catch (Exception e)
{
this.Logger.LogWarning(e, "Could not refresh the log file paths before opening the file manager.");
this.Snackbar.Add(T("The log file path is not available yet."), Severity.Warning, config =>
{
config.Icon = Icons.Material.Filled.Folder;
config.IconSize = Size.Large;
});
return;
}
var path = this.CurrentLogPath;
if (string.IsNullOrWhiteSpace(path))
{
this.Snackbar.Add(T("The log file path is not available yet."), Severity.Warning, config =>
{
config.Icon = Icons.Material.Filled.Folder;
config.IconSize = Size.Large;
});
return;
}
OpenPathResponse response;
try
{
response = await this.RustService.OpenPathInFileManager(path);
}
catch (Exception e)
{
this.Logger.LogWarning(e, "Could not open the log file location in the file manager.");
this.Snackbar.Add(T("Could not open the log file location."), Severity.Error, config =>
{
config.Icon = Icons.Material.Filled.Folder;
config.IconSize = Size.Large;
});
return;
}
if (response.Success)
{
this.Snackbar.Add(T("Opened the log file location."), Severity.Success, config =>
{
config.Icon = Icons.Material.Filled.FolderOpen;
config.IconSize = Size.Large;
});
return;
}
var issue = string.IsNullOrWhiteSpace(response.Issue) ? T("Unknown error") : response.Issue;
this.Snackbar.Add(string.Format(T("Could not open the log file location: {0}"), issue), Severity.Error, config =>
{
config.Icon = Icons.Material.Filled.Folder;
config.IconSize = Size.Large;
});
}
private void ClearFilters() private void ClearFilters()
{ {
this.filterText = string.Empty; this.filterText = string.Empty;

View File

@ -1650,6 +1650,9 @@ UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::LOGVIEWER::ASSISTANTLOGVIEWER::T174782740
-- The log file does not exist: {0} -- The log file does not exist: {0}
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::LOGVIEWER::ASSISTANTLOGVIEWER::T1807514273"] = "Die Protokolldatei existiert nicht: {0}" UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::LOGVIEWER::ASSISTANTLOGVIEWER::T1807514273"] = "Die Protokolldatei existiert nicht: {0}"
-- Could not open the log file location.
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::LOGVIEWER::ASSISTANTLOGVIEWER::T1828231197"] = "Konnte den Speicherort der Protokolldatei nicht öffnen."
-- Other -- Other
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::LOGVIEWER::ASSISTANTLOGVIEWER::T1849229205"] = "Andere" UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::LOGVIEWER::ASSISTANTLOGVIEWER::T1849229205"] = "Andere"
@ -1668,6 +1671,9 @@ UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::LOGVIEWER::ASSISTANTLOGVIEWER::T237835357
-- No matching log lines. -- No matching log lines.
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::LOGVIEWER::ASSISTANTLOGVIEWER::T2511997530"] = "Keine passenden Protokollzeilen." UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::LOGVIEWER::ASSISTANTLOGVIEWER::T2511997530"] = "Keine passenden Protokollzeilen."
-- Could not open the log file location: {0}
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::LOGVIEWER::ASSISTANTLOGVIEWER::T2533784927"] = "Konnte den Speicherort der Protokolldatei nicht öffnen: {0}"
-- Source details -- Source details
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::LOGVIEWER::ASSISTANTLOGVIEWER::T2686813966"] = "Quellendetails" UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::LOGVIEWER::ASSISTANTLOGVIEWER::T2686813966"] = "Quellendetails"
@ -1680,6 +1686,9 @@ UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::LOGVIEWER::ASSISTANTLOGVIEWER::T315262563
-- Loading log file... -- Loading log file...
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::LOGVIEWER::ASSISTANTLOGVIEWER::T333036481"] = "Lade Protokolldatei..." UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::LOGVIEWER::ASSISTANTLOGVIEWER::T333036481"] = "Lade Protokolldatei..."
-- Unknown error
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::LOGVIEWER::ASSISTANTLOGVIEWER::T3461425987"] = "Unbekannter Fehler"
-- The log file path is not available yet. -- The log file path is not available yet.
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::LOGVIEWER::ASSISTANTLOGVIEWER::T3686775689"] = "Der Pfad zur Protokolldatei ist noch nicht verfügbar." UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::LOGVIEWER::ASSISTANTLOGVIEWER::T3686775689"] = "Der Pfad zur Protokolldatei ist noch nicht verfügbar."
@ -1698,12 +1707,18 @@ UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::LOGVIEWER::ASSISTANTLOGVIEWER::T397479987
-- Usage log -- Usage log
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::LOGVIEWER::ASSISTANTLOGVIEWER::T4031747274"] = "Nutzungsprotokoll" UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::LOGVIEWER::ASSISTANTLOGVIEWER::T4031747274"] = "Nutzungsprotokoll"
-- Open in folder
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::LOGVIEWER::ASSISTANTLOGVIEWER::T4048746540"] = "Im Ordner öffnen"
-- Log file -- Log file
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::LOGVIEWER::ASSISTANTLOGVIEWER::T4129341821"] = "Protokolldatei" UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::LOGVIEWER::ASSISTANTLOGVIEWER::T4129341821"] = "Protokolldatei"
-- Log Viewer -- Log Viewer
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::LOGVIEWER::ASSISTANTLOGVIEWER::T4130241777"] = "Protokollanzeige" UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::LOGVIEWER::ASSISTANTLOGVIEWER::T4130241777"] = "Protokollanzeige"
-- Opened the log file location.
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::LOGVIEWER::ASSISTANTLOGVIEWER::T4162897654"] = "Der Speicherort der Protokolldatei wurde geöffnet."
-- Show timestamps -- Show timestamps
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::LOGVIEWER::ASSISTANTLOGVIEWER::T469116133"] = "Zeitstempel anzeigen" UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::LOGVIEWER::ASSISTANTLOGVIEWER::T469116133"] = "Zeitstempel anzeigen"
@ -8586,24 +8601,42 @@ UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::RUSTSERVICE::T1110203516"] = "Fehler
-- Failed to store the API key due to an API issue. -- Failed to store the API key due to an API issue.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::RUSTSERVICE::T1704298921"] = "Fehler beim Speichern des API-Schlüssels aufgrund eines API-Problems." UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::RUSTSERVICE::T1704298921"] = "Fehler beim Speichern des API-Schlüssels aufgrund eines API-Problems."
-- The runtime file manager endpoint returned '{0}'.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::RUSTSERVICE::T2158262203"] = "Der Laufzeit-Dateimanager-Endpunkt hat '{0}' zurückgegeben."
-- Failed to delete the secret data due to an API issue. -- Failed to delete the secret data due to an API issue.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::RUSTSERVICE::T2303057928"] = "Das Löschen der geheimen Daten ist aufgrund eines API-Problems fehlgeschlagen." UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::RUSTSERVICE::T2303057928"] = "Das Löschen der geheimen Daten ist aufgrund eines API-Problems fehlgeschlagen."
-- The runtime file manager endpoint is not available.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::RUSTSERVICE::T2486847754"] = "Der Laufzeit-Dateimanager-Endpunkt ist nicht verfügbar."
-- The runtime file manager endpoint failed without details.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::RUSTSERVICE::T3082220817"] = "Der Laufzeit-Dateimanager-Endpunkt ist ohne Details fehlgeschlagen."
-- Successfully copied the text to your clipboard -- Successfully copied the text to your clipboard
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::RUSTSERVICE::T3351807428"] = "Der Text wurde erfolgreich in die Zwischenablage kopiert." UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::RUSTSERVICE::T3351807428"] = "Der Text wurde erfolgreich in die Zwischenablage kopiert."
-- The path does not exist and its parent folder could not be found.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::RUSTSERVICE::T3606079336"] = "Der Pfad existiert nicht, und der übergeordnete Ordner konnte nicht gefunden werden."
-- Failed to delete the API key due to an API issue. -- Failed to delete the API key due to an API issue.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::RUSTSERVICE::T3658273365"] = "Das API-Schlüssel konnte aufgrund eines API-Problems nicht gelöscht werden." UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::RUSTSERVICE::T3658273365"] = "Das API-Schlüssel konnte aufgrund eines API-Problems nicht gelöscht werden."
-- Failed to copy the text to your clipboard. -- Failed to copy the text to your clipboard.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::RUSTSERVICE::T3724548108"] = "Der Text konnte nicht in die Zwischenablage kopiert werden." UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::RUSTSERVICE::T3724548108"] = "Der Text konnte nicht in die Zwischenablage kopiert werden."
-- The local file manager command failed: {0}
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::RUSTSERVICE::T381756374"] = "Der Befehl des lokalen Dateimanagers ist fehlgeschlagen: {0}"
-- Failed to get the API key due to an API issue. -- Failed to get the API key due to an API issue.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::RUSTSERVICE::T3875720022"] = "Der API-Schlüssel konnte aufgrund eines API-Problems nicht abgerufen werden." UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::RUSTSERVICE::T3875720022"] = "Der API-Schlüssel konnte aufgrund eines API-Problems nicht abgerufen werden."
-- Failed to get the secret data due to an API issue. -- Failed to get the secret data due to an API issue.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::RUSTSERVICE::T4007657575"] = "Abrufen der geheimen Daten aufgrund eines API-Problems fehlgeschlagen." UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::RUSTSERVICE::T4007657575"] = "Abrufen der geheimen Daten aufgrund eines API-Problems fehlgeschlagen."
-- The local file manager command did not start.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::RUSTSERVICE::T613631541"] = "Der Befehl des lokalen Dateimanagers wurde nicht gestartet."
-- No update found. -- No update found.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::UPDATESERVICE::T1015418291"] = "Kein Update gefunden." UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::UPDATESERVICE::T1015418291"] = "Kein Update gefunden."

View File

@ -1650,6 +1650,9 @@ UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::LOGVIEWER::ASSISTANTLOGVIEWER::T174782740
-- The log file does not exist: {0} -- The log file does not exist: {0}
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::LOGVIEWER::ASSISTANTLOGVIEWER::T1807514273"] = "The log file does not exist: {0}" UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::LOGVIEWER::ASSISTANTLOGVIEWER::T1807514273"] = "The log file does not exist: {0}"
-- Could not open the log file location.
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::LOGVIEWER::ASSISTANTLOGVIEWER::T1828231197"] = "Could not open the log file location."
-- Other -- Other
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::LOGVIEWER::ASSISTANTLOGVIEWER::T1849229205"] = "Other" UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::LOGVIEWER::ASSISTANTLOGVIEWER::T1849229205"] = "Other"
@ -1668,6 +1671,9 @@ UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::LOGVIEWER::ASSISTANTLOGVIEWER::T237835357
-- No matching log lines. -- No matching log lines.
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::LOGVIEWER::ASSISTANTLOGVIEWER::T2511997530"] = "No matching log lines." UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::LOGVIEWER::ASSISTANTLOGVIEWER::T2511997530"] = "No matching log lines."
-- Could not open the log file location: {0}
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::LOGVIEWER::ASSISTANTLOGVIEWER::T2533784927"] = "Could not open the log file location: {0}"
-- Source details -- Source details
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::LOGVIEWER::ASSISTANTLOGVIEWER::T2686813966"] = "Source details" UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::LOGVIEWER::ASSISTANTLOGVIEWER::T2686813966"] = "Source details"
@ -1680,6 +1686,9 @@ UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::LOGVIEWER::ASSISTANTLOGVIEWER::T315262563
-- Loading log file... -- Loading log file...
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::LOGVIEWER::ASSISTANTLOGVIEWER::T333036481"] = "Loading log file..." UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::LOGVIEWER::ASSISTANTLOGVIEWER::T333036481"] = "Loading log file..."
-- Unknown error
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::LOGVIEWER::ASSISTANTLOGVIEWER::T3461425987"] = "Unknown error"
-- The log file path is not available yet. -- The log file path is not available yet.
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::LOGVIEWER::ASSISTANTLOGVIEWER::T3686775689"] = "The log file path is not available yet." UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::LOGVIEWER::ASSISTANTLOGVIEWER::T3686775689"] = "The log file path is not available yet."
@ -1698,12 +1707,18 @@ UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::LOGVIEWER::ASSISTANTLOGVIEWER::T397479987
-- Usage log -- Usage log
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::LOGVIEWER::ASSISTANTLOGVIEWER::T4031747274"] = "Usage log" UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::LOGVIEWER::ASSISTANTLOGVIEWER::T4031747274"] = "Usage log"
-- Open in folder
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::LOGVIEWER::ASSISTANTLOGVIEWER::T4048746540"] = "Open in folder"
-- Log file -- Log file
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::LOGVIEWER::ASSISTANTLOGVIEWER::T4129341821"] = "Log file" UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::LOGVIEWER::ASSISTANTLOGVIEWER::T4129341821"] = "Log file"
-- Log Viewer -- Log Viewer
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::LOGVIEWER::ASSISTANTLOGVIEWER::T4130241777"] = "Log Viewer" UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::LOGVIEWER::ASSISTANTLOGVIEWER::T4130241777"] = "Log Viewer"
-- Opened the log file location.
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::LOGVIEWER::ASSISTANTLOGVIEWER::T4162897654"] = "Opened the log file location."
-- Show timestamps -- Show timestamps
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::LOGVIEWER::ASSISTANTLOGVIEWER::T469116133"] = "Show timestamps" UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::LOGVIEWER::ASSISTANTLOGVIEWER::T469116133"] = "Show timestamps"
@ -8586,24 +8601,42 @@ UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::RUSTSERVICE::T1110203516"] = "Failed
-- Failed to store the API key due to an API issue. -- Failed to store the API key due to an API issue.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::RUSTSERVICE::T1704298921"] = "Failed to store the API key due to an API issue." UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::RUSTSERVICE::T1704298921"] = "Failed to store the API key due to an API issue."
-- The runtime file manager endpoint returned '{0}'.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::RUSTSERVICE::T2158262203"] = "The runtime file manager endpoint returned '{0}'."
-- Failed to delete the secret data due to an API issue. -- Failed to delete the secret data due to an API issue.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::RUSTSERVICE::T2303057928"] = "Failed to delete the secret data due to an API issue." UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::RUSTSERVICE::T2303057928"] = "Failed to delete the secret data due to an API issue."
-- The runtime file manager endpoint is not available.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::RUSTSERVICE::T2486847754"] = "The runtime file manager endpoint is not available."
-- The runtime file manager endpoint failed without details.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::RUSTSERVICE::T3082220817"] = "The runtime file manager endpoint failed without details."
-- Successfully copied the text to your clipboard -- Successfully copied the text to your clipboard
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::RUSTSERVICE::T3351807428"] = "Successfully copied the text to your clipboard" UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::RUSTSERVICE::T3351807428"] = "Successfully copied the text to your clipboard"
-- The path does not exist and its parent folder could not be found.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::RUSTSERVICE::T3606079336"] = "The path does not exist and its parent folder could not be found."
-- Failed to delete the API key due to an API issue. -- Failed to delete the API key due to an API issue.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::RUSTSERVICE::T3658273365"] = "Failed to delete the API key due to an API issue." UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::RUSTSERVICE::T3658273365"] = "Failed to delete the API key due to an API issue."
-- Failed to copy the text to your clipboard. -- Failed to copy the text to your clipboard.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::RUSTSERVICE::T3724548108"] = "Failed to copy the text to your clipboard." UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::RUSTSERVICE::T3724548108"] = "Failed to copy the text to your clipboard."
-- The local file manager command failed: {0}
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::RUSTSERVICE::T381756374"] = "The local file manager command failed: {0}"
-- Failed to get the API key due to an API issue. -- Failed to get the API key due to an API issue.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::RUSTSERVICE::T3875720022"] = "Failed to get the API key due to an API issue." UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::RUSTSERVICE::T3875720022"] = "Failed to get the API key due to an API issue."
-- Failed to get the secret data due to an API issue. -- Failed to get the secret data due to an API issue.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::RUSTSERVICE::T4007657575"] = "Failed to get the secret data due to an API issue." UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::RUSTSERVICE::T4007657575"] = "Failed to get the secret data due to an API issue."
-- The local file manager command did not start.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::RUSTSERVICE::T613631541"] = "The local file manager command did not start."
-- No update found. -- No update found.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::UPDATESERVICE::T1015418291"] = "No update found." UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::UPDATESERVICE::T1015418291"] = "No update found."

View File

@ -0,0 +1,3 @@
namespace AIStudio.Tools.Rust;
public readonly record struct OpenPathRequest(string Path);

View File

@ -0,0 +1,3 @@
namespace AIStudio.Tools.Rust;
public readonly record struct OpenPathResponse(bool Success, string Issue);

View File

@ -1,3 +1,5 @@
using System.Diagnostics;
using AIStudio.Tools.Rust; using AIStudio.Tools.Rust;
namespace AIStudio.Tools.Services; namespace AIStudio.Tools.Services;
@ -84,4 +86,109 @@ public sealed partial class RustService
return await result.Content.ReadFromJsonAsync<FileSaveResponse>(this.jsonRustSerializerOptions); return await result.Content.ReadFromJsonAsync<FileSaveResponse>(this.jsonRustSerializerOptions);
} }
public async Task<OpenPathResponse> OpenPathInFileManager(string path)
{
var runtimeResponse = await this.TryOpenPathInRuntimeFileManager(path);
if (runtimeResponse.Success)
return runtimeResponse;
var localResponse = this.TryOpenPathInLocalFileManager(path);
if (localResponse.Success)
return localResponse;
var issue = string.IsNullOrWhiteSpace(runtimeResponse.Issue)
? localResponse.Issue
: $"{runtimeResponse.Issue} {localResponse.Issue}";
return new OpenPathResponse(false, issue.Trim());
}
private async Task<OpenPathResponse> TryOpenPathInRuntimeFileManager(string path)
{
try
{
var result = await this.http.PostAsJsonAsync("/open/path", new OpenPathRequest(path), this.jsonRustSerializerOptions);
if (!result.IsSuccessStatusCode)
{
this.logger!.LogWarning("Failed to open a path in the file manager through the Rust runtime: '{StatusCode}'", result.StatusCode);
return new OpenPathResponse(false, string.Format(TB("The runtime file manager endpoint returned '{0}'."), result.StatusCode));
}
var response = await result.Content.ReadFromJsonAsync<OpenPathResponse>(this.jsonRustSerializerOptions);
return response.Success
? response
: new OpenPathResponse(false, string.IsNullOrWhiteSpace(response.Issue) ? TB("The runtime file manager endpoint failed without details.") : response.Issue);
}
catch (Exception e)
{
this.logger!.LogWarning(e, "Failed to open a path in the file manager through the Rust runtime.");
return new OpenPathResponse(false, TB("The runtime file manager endpoint is not available."));
}
}
private OpenPathResponse TryOpenPathInLocalFileManager(string path)
{
try
{
var target = ResolveFileManagerTarget(path);
if (target is null)
return new OpenPathResponse(false, TB("The path does not exist and its parent folder could not be found."));
using var process = Process.Start(CreateFileManagerStartInfo(target.Value));
return process is null
? new OpenPathResponse(false, TB("The local file manager command did not start."))
: new OpenPathResponse(true, string.Empty);
}
catch (Exception e)
{
this.logger!.LogWarning(e, "Failed to open a path in the local file manager.");
return new OpenPathResponse(false, string.Format(TB("The local file manager command failed: {0}"), e.Message));
}
}
private static FileManagerTarget? ResolveFileManagerTarget(string path)
{
if (string.IsNullOrWhiteSpace(path))
return null;
var requestedPath = path.Trim();
if (File.Exists(requestedPath))
return new FileManagerTarget(requestedPath, true);
if (Directory.Exists(requestedPath))
return new FileManagerTarget(requestedPath, false);
var parent = Directory.GetParent(requestedPath)?.FullName;
return !string.IsNullOrWhiteSpace(parent) && Directory.Exists(parent)
? new FileManagerTarget(parent, false)
: null;
}
private static ProcessStartInfo CreateFileManagerStartInfo(FileManagerTarget target)
{
if (OperatingSystem.IsWindows())
{
var windowsInfo = new ProcessStartInfo("explorer.exe") { UseShellExecute = false };
windowsInfo.ArgumentList.Add(target.RevealFile ? $"/select,{target.Path}" : target.Path);
return windowsInfo;
}
if (OperatingSystem.IsMacOS())
{
var macOsInfo = new ProcessStartInfo("open") { UseShellExecute = false };
if (target.RevealFile)
macOsInfo.ArgumentList.Add("-R");
macOsInfo.ArgumentList.Add(target.Path);
return macOsInfo;
}
var linuxInfo = new ProcessStartInfo("xdg-open") { UseShellExecute = false };
var directory = target.RevealFile ? Path.GetDirectoryName(target.Path) ?? target.Path : target.Path;
linuxInfo.ArgumentList.Add(directory);
return linuxInfo;
}
private readonly record struct FileManagerTarget(string Path, bool RevealFile);
} }

View File

@ -5,6 +5,7 @@
- Improved update guidance for Flatpak installations and added an enterprise option that lets organizations manage updates entirely through their IT department. - Improved update guidance for Flatpak installations and added an enterprise option that lets organizations manage updates entirely through their IT department.
- Fixed an issue that could leave AI Studio unresponsive after waking the computer from sleep. - Fixed an issue that could leave AI Studio unresponsive after waking the computer from sleep.
- Fixed enterprise configuration plugins from Windows-created ZIP files not loading correctly on Linux when the ZIP contained plugin files inside a folder. - Fixed enterprise configuration plugins from Windows-created ZIP files not loading correctly on Linux when the ZIP contained plugin files inside a folder.
- Fixed file extension handling so files are recognized correctly regardless of uppercase or lowercase letters in their extensions.
- Fixed voice recording not starting on Linux. - Fixed voice recording not starting on Linux.
- Upgraded Rust to v1.97.0. - Upgraded Rust to v1.97.0.
- Upgraded Tauri to v2.11.5. - Upgraded Tauri to v2.11.5.

View File

@ -2,10 +2,18 @@
use axum::extract::Query; use axum::extract::Query;
use axum::Json; use axum::Json;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::path::{Path, PathBuf};
use std::process::Command;
#[cfg(windows)]
use std::os::windows::process::CommandExt;
use tauri_plugin_dialog::{DialogExt, FileDialogBuilder}; use tauri_plugin_dialog::{DialogExt, FileDialogBuilder};
use crate::api_token::APIToken; use crate::api_token::APIToken;
use crate::app_window::MAIN_WINDOW; use crate::app_window::MAIN_WINDOW;
/// Microsoft documents CREATE_NO_WINDOW as a process creation flag with value 0x08000000.
#[cfg(windows)]
const CREATE_NO_WINDOW: u32 = 0x08000000;
#[derive(Clone, Deserialize)] #[derive(Clone, Deserialize)]
pub struct PreviousDirectory { pub struct PreviousDirectory {
path: String, path: String,
@ -36,6 +44,11 @@ pub struct SaveFileOptions {
filter: Option<FileTypeFilter>, filter: Option<FileTypeFilter>,
} }
#[derive(Clone, Deserialize)]
pub struct OpenPathOptions {
path: String,
}
#[derive(Serialize)] #[derive(Serialize)]
pub struct DirectorySelectionResponse { pub struct DirectorySelectionResponse {
user_cancelled: bool, user_cancelled: bool,
@ -60,6 +73,12 @@ pub struct FileSaveResponse {
save_file_path: String, save_file_path: String,
} }
#[derive(Serialize)]
pub struct OpenPathResponse {
success: bool,
issue: String,
}
#[derive(Clone, Deserialize)] #[derive(Clone, Deserialize)]
pub struct PreviousFile { pub struct PreviousFile {
file_path: String, file_path: String,
@ -286,6 +305,55 @@ pub async fn save_file(_token: APIToken, payload: Json<SaveFileOptions>) -> Json
} }
} }
pub async fn open_path_in_file_manager(
_token: APIToken,
payload: Json<OpenPathOptions>,
) -> Json<OpenPathResponse> {
let requested_path = PathBuf::from(payload.path.trim());
if requested_path.as_os_str().is_empty() {
return Json(OpenPathResponse {
success: false,
issue: String::from("The path is empty."),
});
}
let Some(target) = resolve_file_manager_target(&requested_path) else {
let issue = format!(
"The path does not exist and its parent folder could not be found: {}",
requested_path.to_string_lossy(),
);
error!(Source = "Tauri"; "{issue}");
return Json(OpenPathResponse {
success: false,
issue,
});
};
let mut command = create_file_manager_command(&target);
#[cfg(windows)]
command.creation_flags(CREATE_NO_WINDOW);
match command.spawn() {
Ok(_) => {
info!("Opened file manager for path: {:?}", target.path);
Json(OpenPathResponse {
success: true,
issue: String::new(),
})
}
Err(error) => {
let issue = format!("Failed to open the file manager: {error}");
error!(Source = "Tauri"; "{issue}");
Json(OpenPathResponse {
success: false,
issue,
})
}
}
}
/// Applies an optional file type filter to a FileDialogBuilder. /// Applies an optional file type filter to a FileDialogBuilder.
fn apply_filter<R: tauri::Runtime>(file_dialog: FileDialogBuilder<R>, filter: &Option<FileTypeFilter>) -> FileDialogBuilder<R> { fn apply_filter<R: tauri::Runtime>(file_dialog: FileDialogBuilder<R>, filter: &Option<FileTypeFilter>) -> FileDialogBuilder<R> {
match filter { match filter {
@ -297,3 +365,67 @@ fn apply_filter<R: tauri::Runtime>(file_dialog: FileDialogBuilder<R>, filter: &O
None => file_dialog, None => file_dialog,
} }
} }
struct FileManagerTarget {
path: PathBuf,
reveal_file: bool,
}
fn resolve_file_manager_target(requested_path: &Path) -> Option<FileManagerTarget> {
if requested_path.is_file() {
return Some(FileManagerTarget {
path: requested_path.to_path_buf(),
reveal_file: true,
});
}
if requested_path.is_dir() {
return Some(FileManagerTarget {
path: requested_path.to_path_buf(),
reveal_file: false,
});
}
requested_path.parent()
.filter(|parent| parent.is_dir())
.map(|parent| FileManagerTarget {
path: parent.to_path_buf(),
reveal_file: false,
})
}
#[cfg(target_os = "windows")]
fn create_file_manager_command(target: &FileManagerTarget) -> Command {
let mut command = Command::new("explorer.exe");
if target.reveal_file {
command.arg(format!("/select,{}", target.path.to_string_lossy()));
} else {
command.arg(&target.path);
}
command
}
#[cfg(target_os = "macos")]
fn create_file_manager_command(target: &FileManagerTarget) -> Command {
let mut command = Command::new("open");
if target.reveal_file {
command.arg("-R");
}
command.arg(&target.path);
command
}
#[cfg(all(unix, not(target_os = "macos")))]
fn create_file_manager_command(target: &FileManagerTarget) -> Command {
let mut command = Command::new("xdg-open");
let directory = if target.reveal_file {
target.path.parent().unwrap_or(&target.path)
} else {
&target.path
};
command.arg(directory);
command
}

View File

@ -46,6 +46,7 @@ pub fn start_runtime_api() {
.route("/select/file", post(crate::file_actions::select_file)) .route("/select/file", post(crate::file_actions::select_file))
.route("/select/files", post(crate::file_actions::select_files)) .route("/select/files", post(crate::file_actions::select_files))
.route("/save/file", post(crate::file_actions::save_file)) .route("/save/file", post(crate::file_actions::save_file))
.route("/open/path", post(crate::file_actions::open_path_in_file_manager))
.route("/secrets/get", post(crate::secret::get_secret)) .route("/secrets/get", post(crate::secret::get_secret))
.route("/secrets/store", post(crate::secret::store_secret)) .route("/secrets/store", post(crate::secret::store_secret))
.route("/secrets/delete", post(crate::secret::delete_secret)) .route("/secrets/delete", post(crate::secret::delete_secret))