mirror of
				https://github.com/MindWorkAI/AI-Studio.git
				synced 2025-10-31 11:40:20 +00:00 
			
		
		
		
	Migrated the clipboard function from Tauri JS to the runtime API using encryption
This commit is contained in:
		
							parent
							
								
									79afd42195
								
							
						
					
					
						commit
						1d12facd58
					
				| @ -165,7 +165,7 @@ public abstract partial class AssistantBase : ComponentBase | |||||||
|      |      | ||||||
|     protected async Task CopyToClipboard() |     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) |     private static string? GetButtonIcon(string icon) | ||||||
|  | |||||||
| @ -100,7 +100,7 @@ public partial class ContentBlockComponent : ComponentBase | |||||||
|         { |         { | ||||||
|             case ContentType.TEXT: |             case ContentType.TEXT: | ||||||
|                 var textContent = (ContentText) this.Content; |                 var textContent = (ContentText) this.Content; | ||||||
|                 await this.Rust.CopyText2Clipboard(this.JsRuntime, this.Snackbar, textContent.Text); |                 await this.Rust.CopyText2Clipboard(this.Snackbar, textContent.Text); | ||||||
|                 break; |                 break; | ||||||
|              |              | ||||||
|             default: |             default: | ||||||
|  | |||||||
| @ -78,36 +78,44 @@ public sealed class Rust(string apiPort) : IDisposable | |||||||
|     /// <summary> |     /// <summary> | ||||||
|     /// Tries to copy the given text to the clipboard. |     /// Tries to copy the given text to the clipboard. | ||||||
|     /// </summary> |     /// </summary> | ||||||
|     /// <param name="jsRuntime">The JS runtime to access the Rust code.</param> |  | ||||||
|     /// <param name="snackbar">The snackbar to show the result.</param> |     /// <param name="snackbar">The snackbar to show the result.</param> | ||||||
|     /// <param name="text">The text to copy to the clipboard.</param> |     /// <param name="text">The text to copy to the clipboard.</param> | ||||||
|     public async Task CopyText2Clipboard(IJSRuntime jsRuntime, ISnackbar snackbar, string text) |     public async Task CopyText2Clipboard(ISnackbar snackbar, string text) | ||||||
|     { |     { | ||||||
|         var response = await jsRuntime.InvokeAsync<SetClipboardResponse>("window.__TAURI__.invoke", "set_clipboard", new SetClipboardText(text)); |         var message = "Successfully copied the text to your clipboard"; | ||||||
|         var msg = response.Success switch |         var iconColor = Color.Error; | ||||||
|  |         var severity = Severity.Error; | ||||||
|  |         try | ||||||
|         { |         { | ||||||
|             true => "Successfully copied text to clipboard!", |             var response = await this.http.PostAsync("/clipboard/set", new StringContent(await text.Encrypt(this.encryptor!))); | ||||||
|             false => $"Failed to copy text to clipboard: {response.Issue}", |             if (!response.IsSuccessStatusCode) | ||||||
|         }; |  | ||||||
|                  |  | ||||||
|         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 |  | ||||||
|             { |             { | ||||||
|                 Severity.Success => Color.Success, |                 this.logger!.LogError($"Failed to copy the text to the clipboard due to an network error: '{response.StatusCode}'"); | ||||||
|                 Severity.Error => Color.Error, |                 message = "Failed to copy the text to your clipboard."; | ||||||
|                          |                 return; | ||||||
|                 _ => Color.Default, |             } | ||||||
|             }; | 
 | ||||||
|         }); |             var state = await response.Content.ReadFromJsonAsync<SetClipboardResponse>(); | ||||||
|  |             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<UpdateResponse> CheckForUpdate(IJSRuntime jsRuntime) |     public async Task<UpdateResponse> CheckForUpdate(IJSRuntime jsRuntime) | ||||||
|  | |||||||
| @ -6,10 +6,8 @@ namespace AIStudio.Tools.Services; | |||||||
| /// Wire up the clipboard service to copy Markdown to the clipboard. | /// Wire up the clipboard service to copy Markdown to the clipboard. | ||||||
| /// We use our own Rust-based clipboard service for this. | /// We use our own Rust-based clipboard service for this. | ||||||
| /// </summary> | /// </summary> | ||||||
| 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 ISnackbar Snackbar { get; } = snackbar; | ||||||
|      |      | ||||||
|     private Rust Rust { get; } = rust; |     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. |     /// Gets called when the user wants to copy the Markdown to the clipboard. | ||||||
|     /// </summary> |     /// </summary> | ||||||
|     /// <param name="text">The Markdown text to copy.</param> |     /// <param name="text">The Markdown text to copy.</param> | ||||||
|     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); | ||||||
| } | } | ||||||
| @ -194,7 +194,7 @@ async fn main() { | |||||||
|     //
 |     //
 | ||||||
|     tauri::async_runtime::spawn(async move { |     tauri::async_runtime::spawn(async move { | ||||||
|         _ = rocket::custom(figment) |         _ = rocket::custom(figment) | ||||||
|             .mount("/", routes![dotnet_port, dotnet_ready]) |             .mount("/", routes![dotnet_port, dotnet_ready, set_clipboard]) | ||||||
|             .ignite().await.unwrap() |             .ignite().await.unwrap() | ||||||
|             .launch().await.unwrap(); |             .launch().await.unwrap(); | ||||||
|     }); |     }); | ||||||
| @ -312,7 +312,7 @@ async fn main() { | |||||||
|         }) |         }) | ||||||
|         .plugin(tauri_plugin_window_state::Builder::default().build()) |         .plugin(tauri_plugin_window_state::Builder::default().build()) | ||||||
|         .invoke_handler(tauri::generate_handler![ |         .invoke_handler(tauri::generate_handler![ | ||||||
|             store_secret, get_secret, delete_secret, set_clipboard, |             store_secret, get_secret, delete_secret, | ||||||
|             check_for_update, install_update |             check_for_update, install_update | ||||||
|         ]) |         ]) | ||||||
|         .build(tauri::generate_context!()) |         .build(tauri::generate_context!()) | ||||||
| @ -848,36 +848,49 @@ struct DeleteSecretResponse { | |||||||
|     issue: String, |     issue: String, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| #[tauri::command] | #[post("/clipboard/set", data = "<encrypted_text>")] | ||||||
| fn set_clipboard(text: String) -> SetClipboardResponse { | fn set_clipboard(encrypted_text: EncryptedText) -> Json<SetClipboardResponse> { | ||||||
|  | 
 | ||||||
|  |     // 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 clipboard_result = Clipboard::new(); | ||||||
|     let mut clipboard = match clipboard_result { |     let mut clipboard = match clipboard_result { | ||||||
|         Ok(clipboard) => clipboard, |         Ok(clipboard) => clipboard, | ||||||
|         Err(e) => { |         Err(e) => { | ||||||
|             error!(Source = "Clipboard"; "Failed to get the clipboard instance: {e}."); |             error!(Source = "Clipboard"; "Failed to get the clipboard instance: {e}."); | ||||||
|             return SetClipboardResponse { |             return Json(SetClipboardResponse { | ||||||
|                 success: false, |                 success: false, | ||||||
|                 issue: e.to_string(), |                 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 { |     match set_text_result { | ||||||
|         Ok(_) => { |         Ok(_) => { | ||||||
|             debug!(Source = "Clipboard"; "Text was set to the clipboard successfully."); |             debug!(Source = "Clipboard"; "Text was set to the clipboard successfully."); | ||||||
|             SetClipboardResponse { |             Json(SetClipboardResponse { | ||||||
|                 success: true, |                 success: true, | ||||||
|                 issue: String::from(""), |                 issue: String::from(""), | ||||||
|             } |             }) | ||||||
|         }, |         }, | ||||||
|         
 |         
 | ||||||
|         Err(e) => { |         Err(e) => { | ||||||
|             error!(Source = "Clipboard"; "Failed to set text to the clipboard: {e}."); |             error!(Source = "Clipboard"; "Failed to set text to the clipboard: {e}."); | ||||||
|             SetClipboardResponse { |             Json(SetClipboardResponse { | ||||||
|                 success: false, |                 success: false, | ||||||
|                 issue: e.to_string(), |                 issue: e.to_string(), | ||||||
|             } |             }) | ||||||
|         }, |         }, | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | |||||||
		Loading…
	
		Reference in New Issue
	
	Block a user