diff options
| author | Brendan Allan <[email protected]> | 2025-12-18 17:17:31 +0800 |
|---|---|---|
| committer | GitHub <[email protected]> | 2025-12-18 03:17:31 -0600 |
| commit | b70d186bd1e33adea9efdfc6d08788bffa9056f6 (patch) | |
| tree | e5cdcc190d4f7fddea72a7718bd743e8b3127868 /packages/tauri/src-tauri/src | |
| parent | 647331de28fc27052aa530b4e6d89ff3beb4ec11 (diff) | |
| download | opencode-b70d186bd1e33adea9efdfc6d08788bffa9056f6.tar.gz opencode-b70d186bd1e33adea9efdfc6d08788bffa9056f6.zip | |
tauri: server spawn fail dialog w/ copy logs button (#5729)
Diffstat (limited to 'packages/tauri/src-tauri/src')
| -rw-r--r-- | packages/tauri/src-tauri/src/lib.rs | 96 |
1 files changed, 89 insertions, 7 deletions
diff --git a/packages/tauri/src-tauri/src/lib.rs b/packages/tauri/src-tauri/src/lib.rs index 08bec528d..f39096031 100644 --- a/packages/tauri/src-tauri/src/lib.rs +++ b/packages/tauri/src-tauri/src/lib.rs @@ -1,4 +1,5 @@ use std::{ + collections::VecDeque, net::{SocketAddr, TcpListener}, sync::{Arc, Mutex}, time::{Duration, Instant}, @@ -6,6 +7,8 @@ use std::{ #[cfg(target_os = "macos")] use tauri::TitleBarStyle; use tauri::{AppHandle, LogicalSize, Manager, RunEvent, WebviewUrl, WebviewWindow}; +use tauri_plugin_clipboard_manager::ClipboardExt; +use tauri_plugin_dialog::{DialogExt, MessageDialogButtons, MessageDialogResult}; use tauri_plugin_shell::process::{CommandChild, CommandEvent}; use tauri_plugin_shell::ShellExt; use tokio::net::TcpSocket; @@ -13,6 +16,11 @@ use tokio::net::TcpSocket; #[derive(Clone)] struct ServerState(Arc<Mutex<Option<CommandChild>>>); +#[derive(Clone)] +struct LogState(Arc<Mutex<VecDeque<String>>>); + +const MAX_LOG_ENTRIES: usize = 200; + #[tauri::command] fn kill_sidecar(app: AppHandle) { let Some(server_state) = app.try_state::<ServerState>() else { @@ -35,7 +43,37 @@ fn kill_sidecar(app: AppHandle) { println!("Killed server"); } -fn get_sidecar_port() -> u16 { +#[tauri::command] +async fn copy_logs_to_clipboard(app: AppHandle) -> Result<(), String> { + let log_state = app.try_state::<LogState>().ok_or("Log state not found")?; + + let logs = log_state + .0 + .lock() + .map_err(|_| "Failed to acquire log lock")?; + + let log_text = logs.iter().cloned().collect::<Vec<_>>().join(""); + + app.clipboard() + .write_text(log_text) + .map_err(|e| format!("Failed to copy to clipboard: {}", e))?; + + Ok(()) +} + +#[tauri::command] +async fn get_logs(app: AppHandle) -> Result<String, String> { + let log_state = app.try_state::<LogState>().ok_or("Log state not found")?; + + let logs = log_state + .0 + .lock() + .map_err(|_| "Failed to acquire log lock")?; + + Ok(logs.iter().cloned().collect::<Vec<_>>().join("")) +} + +fn get_sidecar_port() -> u32 { option_env!("OPENCODE_PORT") .map(|s| s.to_string()) .or_else(|| std::env::var("OPENCODE_PORT").ok()) @@ -46,14 +84,17 @@ fn get_sidecar_port() -> u16 { .local_addr() .expect("Failed to get local address") .port() - }) + }) as u32 } fn get_user_shell() -> String { std::env::var("SHELL").unwrap_or_else(|_| "/bin/sh".to_string()) } -fn spawn_sidecar(app: &AppHandle, port: u16) -> CommandChild { +fn spawn_sidecar(app: &AppHandle, port: u32) -> CommandChild { + let log_state = app.state::<LogState>(); + let log_state_clone = log_state.inner().clone(); + #[cfg(target_os = "windows")] let (mut rx, child) = app .shell() @@ -92,10 +133,28 @@ fn spawn_sidecar(app: &AppHandle, port: u16) -> CommandChild { CommandEvent::Stdout(line_bytes) => { let line = String::from_utf8_lossy(&line_bytes); print!("{line}"); + + // Store log in shared state + if let Ok(mut logs) = log_state_clone.0.lock() { + logs.push_back(format!("[STDOUT] {}", line)); + // Keep only the last MAX_LOG_ENTRIES + while logs.len() > MAX_LOG_ENTRIES { + logs.pop_front(); + } + } } CommandEvent::Stderr(line_bytes) => { let line = String::from_utf8_lossy(&line_bytes); eprint!("{line}"); + + // Store log in shared state + if let Ok(mut logs) = log_state_clone.0.lock() { + logs.push_back(format!("[STDERR] {}", line)); + // Keep only the last MAX_LOG_ENTRIES + while logs.len() > MAX_LOG_ENTRIES { + logs.pop_front(); + } + } } _ => {} } @@ -105,12 +164,12 @@ fn spawn_sidecar(app: &AppHandle, port: u16) -> CommandChild { child } -async fn is_server_running(port: u16) -> bool { +async fn is_server_running(port: u32) -> bool { TcpSocket::new_v4() .unwrap() .connect(SocketAddr::new( "127.0.0.1".parse().expect("Failed to parse IP"), - port, + port as u16, )) .await .is_ok() @@ -128,10 +187,18 @@ pub fn run() { .plugin(tauri_plugin_shell::init()) .plugin(tauri_plugin_process::init()) .plugin(tauri_plugin_opener::init()) - .invoke_handler(tauri::generate_handler![kill_sidecar]) + .plugin(tauri_plugin_clipboard_manager::init()) + .invoke_handler(tauri::generate_handler![ + kill_sidecar, + copy_logs_to_clipboard, + get_logs + ]) .setup(move |app| { let app = app.handle().clone(); + // Initialize log state + app.manage(LogState(Arc::new(Mutex::new(VecDeque::new())))); + tauri::async_runtime::spawn(async move { let port = get_sidecar_port(); @@ -143,7 +210,22 @@ pub fn run() { let timestamp = Instant::now(); loop { if timestamp.elapsed() > Duration::from_secs(7) { - todo!("Handle server spawn timeout"); + let res = app.dialog() + .message("Failed to spawn OpenCode CLI. Copy logs using the button below and send them to the team for assistance.") + .title("Startup Failed") + .buttons(MessageDialogButtons::OkCancelCustom("Copy Logs And Exit".to_string(), "Exit".to_string())) + .blocking_show_with_result(); + + if matches!(&res, MessageDialogResult::Custom(name) if name == "Copy Logs And Exit") { + match copy_logs_to_clipboard(app.clone()).await { + Ok(()) => println!("Logs copied to clipboard successfully"), + Err(e) => println!("Failed to copy logs to clipboard: {}", e), + } + } + + app.exit(1); + + return; } tokio::time::sleep(Duration::from_millis(10)).await; |
