robin/src-tauri/src/commands.rs
pyr0ball f0269c62a5 fix(build): add reqwest, fix exhaustive match, tauri deprecation, Cargo.lock
- Add reqwest 0.12 (json + stream features) — was missing from Cargo.toml
  causing chat_stream to fail to compile
- Add Android/IpadOs arms to SourceOs match in chat command
- Add tauri::Manager import to notify.rs (needed for .state())
- Replace deprecated menu_on_left_click with show_menu_on_left_click
- Remove desktopTemplate from tauri.conf.json (not in tauri-utils 2.9.2 schema)
- Commit Cargo.lock so cross-machine builds pin identical crate versions

All 42 unit tests pass on Linux Mint 22.3 (Muninn).
2026-05-20 08:32:39 -07:00

146 lines
4.5 KiB
Rust

use crate::config::{MigrationConfig, NotificationLevel, RobinConfig, SourceOs};
use std::sync::Mutex;
use tauri::{Emitter, State};
pub struct AppState {
pub config: Mutex<RobinConfig>,
}
#[tauri::command]
pub fn get_config(state: State<'_, AppState>) -> Result<RobinConfig, String> {
state
.config
.lock()
.map(|c| c.clone())
.map_err(|e| e.to_string())
}
#[tauri::command]
pub fn needs_onboarding(state: State<'_, AppState>) -> bool {
state
.config
.lock()
.map(|c| c.needs_onboarding())
.unwrap_or(true)
}
#[tauri::command]
pub fn complete_onboarding(
source_os: String,
distro: String,
source_distro: Option<String>,
dual_boot_with: Option<String>,
state: State<'_, AppState>,
) -> Result<(), String> {
let source = match source_os.to_lowercase().as_str() {
"macos" | "mac" => SourceOs::Macos,
"windows" => SourceOs::Windows,
"linux" => SourceOs::Linux,
"android" => SourceOs::Android,
"ipad" | "ios" | "ipados" => SourceOs::IpadOs,
_ => SourceOs::Unknown,
};
let detected = if distro == "unknown" || distro.is_empty() {
crate::distro::detect()
} else {
distro
};
let source_distro_family = source_distro.as_deref().and_then(|sd| {
let family = crate::distro::distro_family(sd);
if family == "unknown" { None } else { Some(family.to_string()) }
});
// Normalise dual_boot_with to a canonical name; reject unrecognised values.
let dual_boot_with = dual_boot_with.and_then(|s| match s.to_lowercase().as_str() {
"windows" => Some("windows".to_string()),
"macos" | "mac" => Some("macos".to_string()),
_ => None,
});
let mut config = state.config.lock().map_err(|e| e.to_string())?;
config.migration = Some(MigrationConfig {
source_os: source,
distro: detected,
source_distro_family,
dual_boot_with,
fluency_level: 0,
});
config.save().map_err(|e| e.to_string())
}
#[tauri::command]
pub fn update_notification_level(level: String, state: State<'_, AppState>) -> Result<(), String> {
let parsed = match level.as_str() {
"off" => NotificationLevel::Off,
"badge_only" => NotificationLevel::BadgeOnly,
"badge_and_toast" => NotificationLevel::BadgeAndToast,
_ => return Err(format!("unknown notification level: {level}")),
};
let mut config = state.config.lock().map_err(|e| e.to_string())?;
config.display.notification_level = parsed;
config.save().map_err(|e| e.to_string())
}
#[tauri::command]
pub fn get_pending_events() -> Vec<crate::patterns::MatchedEvent> {
crate::notify::take_pending()
}
#[tauri::command]
pub fn panel_opened() {
crate::notify::set_panel_open(true);
}
#[tauri::command]
pub fn panel_closed() {
crate::notify::set_panel_open(false);
}
#[tauri::command]
pub async fn chat(
message: String,
state: State<'_, AppState>,
app_handle: tauri::AppHandle,
) -> Result<(), String> {
let (base_url, model, source_os, distro) = {
let cfg = state.config.lock().map_err(|e| e.to_string())?;
let (source_os, distro) = if let Some(ref m) = cfg.migration {
let os = match m.source_os {
crate::config::SourceOs::Macos => "macOS",
crate::config::SourceOs::Windows => "Windows",
crate::config::SourceOs::Linux => "Linux",
crate::config::SourceOs::Android => "Android",
crate::config::SourceOs::IpadOs => "iPad/iOS",
crate::config::SourceOs::Unknown => "Unknown",
};
(os.to_string(), m.distro.clone())
} else {
("Unknown".to_string(), "unknown".to_string())
};
(
cfg.ollama.base_url.clone(),
cfg.ollama.model.clone(),
source_os,
distro,
)
};
let system_prompt = crate::llm::build_system_prompt(&source_os, &distro);
let messages = vec![
crate::llm::ChatMessage { role: "system".into(), content: system_prompt },
crate::llm::ChatMessage { role: "user".into(), content: message },
];
tauri::async_runtime::spawn(async move {
if let Err(e) = crate::llm::chat_stream(&base_url, &model, messages, &app_handle).await {
log::error!("chat stream error: {e}");
if let Err(emit_err) = app_handle.emit("robin:chat-error", e.to_string()) {
log::warn!("failed to emit robin:chat-error: {emit_err}");
}
}
});
Ok(())
}