- 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).
146 lines
4.5 KiB
Rust
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(())
|
|
}
|