Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions assistant_v2/FEATURE_PROGRESS.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 |
Expand Down
45 changes: 45 additions & 0 deletions assistant_v2/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,17 @@ async fn main() -> Result<(), Box<dyn Error>> {
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(
Expand Down Expand Up @@ -519,6 +530,14 @@ fn get_system_info() -> String {
info
}

fn get_clipboard_string() -> Result<String, String> {
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<OpenAIConfig>,
run_object: RunObject,
Expand Down Expand Up @@ -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::<serde_json::Value>(&tool.function.arguments) {
Expand Down Expand Up @@ -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(_) => {}
}
}
}
40 changes: 40 additions & 0 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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::<serde_json::Value>(fn_args) {
Ok(json) => json,
Expand Down Expand Up @@ -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<String, String> {
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<NamedTempFile> =
LazyLock::new(|| temp_asset!("../assets/failed.mp3"));

Expand Down Expand Up @@ -1591,6 +1621,16 @@ async fn main() -> Result<(), Box<dyn Error>> {
}))
.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.")
Expand Down