From 5e8162588066c776dd98e3a20cd5651d1b55639c Mon Sep 17 00:00:00 2001 From: Logan King Date: Fri, 18 Jul 2025 19:21:05 -0700 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20Add=20clipboard=20read=20function?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 1 + assistant_v2/FEATURE_PROGRESS.md | 1 + assistant_v2/src/main.rs | 45 ++++++++++++++++++++++++++++++++ src/main.rs | 40 ++++++++++++++++++++++++++++ 4 files changed, 87 insertions(+) diff --git a/README.md b/README.md index f7e0c92..831dd92 100644 --- a/README.md +++ b/README.md @@ -27,6 +27,7 @@ https://github.com/sloganking/quick-assistant/assets/16965931/a0c7469a-2c64-46e5 - 🗑️ **List and kill processes** by voice - 🌐 **Run internet speed tests** - 📋 **Set the clipboard** contents +- 📋 **Get the clipboard** contents - 🔳 **Copy text as a QR code image** to the clipboard - ⏱️ **Timers** with alarm sounds - 🎙️ **Change voice** or speaking speed on the fly diff --git a/assistant_v2/FEATURE_PROGRESS.md b/assistant_v2/FEATURE_PROGRESS.md index 34aff05..da70e56 100644 --- a/assistant_v2/FEATURE_PROGRESS.md +++ b/assistant_v2/FEATURE_PROGRESS.md @@ -13,6 +13,7 @@ This document tracks which features from the original assistant have been implem | List and kill processes | Pending | | Run internet speed tests | Pending | | Set the clipboard contents | Done | +| Get the clipboard contents | Done | | Timers with alarm sounds | Pending | | Change voice or speaking speed | Done | | Mute/unmute voice output | Done | diff --git a/assistant_v2/src/main.rs b/assistant_v2/src/main.rs index addf8a9..b5a374c 100644 --- a/assistant_v2/src/main.rs +++ b/assistant_v2/src/main.rs @@ -186,6 +186,17 @@ async fn main() -> Result<(), Box> { strict: None, } .into(), + FunctionObject { + name: "get_clipboard".into(), + description: Some("Returns the current clipboard text.".into()), + parameters: Some(serde_json::json!({ + "type": "object", + "properties": {}, + "required": [], + })), + strict: None, + } + .into(), FunctionObject { name: "open_openai_billing".into(), description: Some( @@ -519,6 +530,14 @@ fn get_system_info() -> String { info } +fn get_clipboard_string() -> Result { + let mut clipboard: ClipboardContext = ClipboardProvider::new() + .map_err(|e| format!("Failed to initialize clipboard: {}", e))?; + clipboard + .get_contents() + .map_err(|e| format!("Failed to read clipboard contents: {}", e)) +} + async fn handle_requires_action( client: Client, run_object: RunObject, @@ -561,6 +580,17 @@ async fn handle_requires_action( }); } + if tool.function.name == "get_clipboard" { + let msg = match get_clipboard_string() { + Ok(text) => text, + Err(e) => e, + }; + tool_outputs.push(ToolsOutputs { + tool_call_id: Some(tool.id.clone()), + output: Some(msg.into()), + }); + } + if tool.function.name == "set_screen_brightness" { let brightness = match serde_json::from_str::(&tool.function.arguments) { @@ -957,4 +987,19 @@ mod tests { _ => false, })); } + + #[test] + fn get_clipboard_returns_contents() { + let mut clipboard: ClipboardContext = match ClipboardProvider::new() { + Ok(c) => c, + Err(_) => return, + }; + if clipboard.set_contents("clipboard_test".to_string()).is_err() { + return; + } + match get_clipboard_string() { + Ok(contents) => assert_eq!(contents, "clipboard_test"), + Err(_) => {} + } + } } diff --git a/src/main.rs b/src/main.rs index 9e9bba9..9fb230e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -185,6 +185,21 @@ mod tests { assert_eq!(img.height, 128); assert_eq!(img.bytes.len(), 128 * 128 * 4); } + + #[test] + fn clipboard_roundtrip() { + let mut clipboard: ClipboardContext = match ClipboardProvider::new() { + Ok(c) => c, + Err(_) => return, // clipboard not available + }; + if clipboard.set_contents("test123".to_string()).is_err() { + return; + } + match get_clipboard_string() { + Ok(contents) => assert_eq!(contents, "test123"), + Err(_) => {} + } + } } /// Creates a temporary file from a byte slice and returns the path to the file. @@ -577,6 +592,13 @@ fn call_fn( } } + "get_clipboard" => { + match get_clipboard_string() { + Ok(text) => Some(text), + Err(e) => Some(e), + } + } + "qr_to_clipboard" => { let args = match serde_json::from_str::(fn_args) { Ok(json) => json, @@ -985,6 +1007,14 @@ fn qr_to_clipboard(text: &str) -> Result<(), String> { .map_err(|e| format!("Failed to set clipboard image: {}", e)) } +fn get_clipboard_string() -> Result { + let mut clipboard: ClipboardContext = ClipboardProvider::new() + .map_err(|e| format!("Failed to initialize clipboard: {}", e))?; + clipboard + .get_contents() + .map_err(|e| format!("Failed to read clipboard contents: {}", e)) +} + static FAILED_TEMP_FILE: LazyLock = LazyLock::new(|| temp_asset!("../assets/failed.mp3")); @@ -1591,6 +1621,16 @@ async fn main() -> Result<(), Box> { })) .build().unwrap(), + ChatCompletionFunctionsArgs::default() + .name("get_clipboard") + .description("Returns the current clipboard text.") + .parameters(json!({ + "type": "object", + "properties": {}, + "required": [], + })) + .build().unwrap(), + ChatCompletionFunctionsArgs::default() .name("qr_to_clipboard") .description("Copies a QR code image for the given text to the clipboard.")