diff --git a/app/MindWork AI Studio/Assistants/AssistantBase.razor.cs b/app/MindWork AI Studio/Assistants/AssistantBase.razor.cs index bf1e8ddf..83083278 100644 --- a/app/MindWork AI Studio/Assistants/AssistantBase.razor.cs +++ b/app/MindWork AI Studio/Assistants/AssistantBase.razor.cs @@ -165,7 +165,7 @@ public abstract partial class AssistantBase : ComponentBase protected async Task CopyToClipboard() { - await this.Rust.CopyText2Clipboard(this.JsRuntime, this.Snackbar, this.Result2Copy()); + await this.Rust.CopyText2Clipboard(this.Snackbar, this.Result2Copy()); } private static string? GetButtonIcon(string icon) diff --git a/app/MindWork AI Studio/Chat/ContentBlockComponent.razor.cs b/app/MindWork AI Studio/Chat/ContentBlockComponent.razor.cs index f411b075..8bb48b4a 100644 --- a/app/MindWork AI Studio/Chat/ContentBlockComponent.razor.cs +++ b/app/MindWork AI Studio/Chat/ContentBlockComponent.razor.cs @@ -100,7 +100,7 @@ public partial class ContentBlockComponent : ComponentBase { case ContentType.TEXT: var textContent = (ContentText) this.Content; - await this.Rust.CopyText2Clipboard(this.JsRuntime, this.Snackbar, textContent.Text); + await this.Rust.CopyText2Clipboard(this.Snackbar, textContent.Text); break; default: diff --git a/app/MindWork AI Studio/Tools/Rust.cs b/app/MindWork AI Studio/Tools/Rust.cs index b7d158bd..d78f8a0f 100644 --- a/app/MindWork AI Studio/Tools/Rust.cs +++ b/app/MindWork AI Studio/Tools/Rust.cs @@ -78,36 +78,44 @@ public sealed class Rust(string apiPort) : IDisposable /// /// Tries to copy the given text to the clipboard. /// - /// The JS runtime to access the Rust code. /// The snackbar to show the result. /// The text to copy to the clipboard. - public async Task CopyText2Clipboard(IJSRuntime jsRuntime, ISnackbar snackbar, string text) + public async Task CopyText2Clipboard(ISnackbar snackbar, string text) { - var response = await jsRuntime.InvokeAsync("window.__TAURI__.invoke", "set_clipboard", new SetClipboardText(text)); - var msg = response.Success switch + var message = "Successfully copied the text to your clipboard"; + var iconColor = Color.Error; + var severity = Severity.Error; + try { - true => "Successfully copied text to clipboard!", - false => $"Failed to copy text to clipboard: {response.Issue}", - }; - - var severity = response.Success switch - { - true => Severity.Success, - false => Severity.Error, - }; - - snackbar.Add(msg, severity, config => - { - config.Icon = Icons.Material.Filled.ContentCopy; - config.IconSize = Size.Large; - config.IconColor = severity switch + var response = await this.http.PostAsync("/clipboard/set", new StringContent(await text.Encrypt(this.encryptor!))); + if (!response.IsSuccessStatusCode) { - Severity.Success => Color.Success, - Severity.Error => Color.Error, - - _ => Color.Default, - }; - }); + this.logger!.LogError($"Failed to copy the text to the clipboard due to an network error: '{response.StatusCode}'"); + message = "Failed to copy the text to your clipboard."; + return; + } + + var state = await response.Content.ReadFromJsonAsync(); + if (!state.Success) + { + this.logger!.LogError("Failed to copy the text to the clipboard."); + message = "Failed to copy the text to your clipboard."; + return; + } + + iconColor = Color.Success; + severity = Severity.Success; + this.logger!.LogDebug("Successfully copied the text to the clipboard."); + } + finally + { + snackbar.Add(message, severity, config => + { + config.Icon = Icons.Material.Filled.ContentCopy; + config.IconSize = Size.Large; + config.IconColor = iconColor; + }); + } } public async Task CheckForUpdate(IJSRuntime jsRuntime) diff --git a/app/MindWork AI Studio/Tools/Services/MarkdownClipboardService.cs b/app/MindWork AI Studio/Tools/Services/MarkdownClipboardService.cs index 2684b2a9..4b566ab4 100644 --- a/app/MindWork AI Studio/Tools/Services/MarkdownClipboardService.cs +++ b/app/MindWork AI Studio/Tools/Services/MarkdownClipboardService.cs @@ -6,10 +6,8 @@ namespace AIStudio.Tools.Services; /// Wire up the clipboard service to copy Markdown to the clipboard. /// We use our own Rust-based clipboard service for this. /// -public sealed class MarkdownClipboardService(Rust rust, IJSRuntime jsRuntime, ISnackbar snackbar) : IMudMarkdownClipboardService +public sealed class MarkdownClipboardService(Rust rust, ISnackbar snackbar) : IMudMarkdownClipboardService { - private IJSRuntime JsRuntime { get; } = jsRuntime; - private ISnackbar Snackbar { get; } = snackbar; private Rust Rust { get; } = rust; @@ -18,5 +16,5 @@ public sealed class MarkdownClipboardService(Rust rust, IJSRuntime jsRuntime, IS /// Gets called when the user wants to copy the Markdown to the clipboard. /// /// The Markdown text to copy. - public async ValueTask CopyToClipboardAsync(string text) => await this.Rust.CopyText2Clipboard(this.JsRuntime, this.Snackbar, text); + public async ValueTask CopyToClipboardAsync(string text) => await this.Rust.CopyText2Clipboard(this.Snackbar, text); } \ No newline at end of file diff --git a/runtime/src/main.rs b/runtime/src/main.rs index 9116e5ff..aa14fc60 100644 --- a/runtime/src/main.rs +++ b/runtime/src/main.rs @@ -194,7 +194,7 @@ async fn main() { // tauri::async_runtime::spawn(async move { _ = rocket::custom(figment) - .mount("/", routes![dotnet_port, dotnet_ready]) + .mount("/", routes![dotnet_port, dotnet_ready, set_clipboard]) .ignite().await.unwrap() .launch().await.unwrap(); }); @@ -312,7 +312,7 @@ async fn main() { }) .plugin(tauri_plugin_window_state::Builder::default().build()) .invoke_handler(tauri::generate_handler![ - store_secret, get_secret, delete_secret, set_clipboard, + store_secret, get_secret, delete_secret, check_for_update, install_update ]) .build(tauri::generate_context!()) @@ -848,36 +848,49 @@ struct DeleteSecretResponse { issue: String, } -#[tauri::command] -fn set_clipboard(text: String) -> SetClipboardResponse { +#[post("/clipboard/set", data = "")] +fn set_clipboard(encrypted_text: EncryptedText) -> Json { + + // Decrypt this text first: + let decrypted_text = match ENCRYPTION.decrypt(&encrypted_text) { + Ok(text) => text, + Err(e) => { + error!(Source = "Clipboard"; "Failed to decrypt the text: {e}."); + return Json(SetClipboardResponse { + success: false, + issue: e, + }) + }, + }; + let clipboard_result = Clipboard::new(); let mut clipboard = match clipboard_result { Ok(clipboard) => clipboard, Err(e) => { error!(Source = "Clipboard"; "Failed to get the clipboard instance: {e}."); - return SetClipboardResponse { + return Json(SetClipboardResponse { success: false, issue: e.to_string(), - } + }) }, }; - let set_text_result = clipboard.set_text(text); + let set_text_result = clipboard.set_text(decrypted_text); match set_text_result { Ok(_) => { debug!(Source = "Clipboard"; "Text was set to the clipboard successfully."); - SetClipboardResponse { + Json(SetClipboardResponse { success: true, issue: String::from(""), - } + }) }, Err(e) => { error!(Source = "Clipboard"; "Failed to set text to the clipboard: {e}."); - SetClipboardResponse { + Json(SetClipboardResponse { success: false, issue: e.to_string(), - } + }) }, } }