cueward-adapter-macos 0.5.0

macOS adapter for Cueward with Safari, Notes, Messages, Reminders, Calendar, Screenshot, Clipboard, and OCR integrations.
use std::thread;
use std::time::Duration;

use serde_json::Value;

use crate::MacosError;
use crate::safari_guard::with_safari_session;

use super::super::super::core::execute_js_for_profile;
use super::super::super::script::escape_js_string;
use super::super::{SafariAiResponseResult, SafariConversation};

pub fn gemini_list_conversations(
    profile_filter: Option<&str>,
) -> Result<Vec<SafariConversation>, MacosError> {
    with_safari_session(|| {
        let js = r#"(() => {
        const items = document.querySelectorAll('a[href*="/app/"]');
        const convos = [];
        for (const a of items) {
          const href = a.getAttribute("href") || "";
          if (!/\/app\/[a-f0-9]{10,}/.test(href)) continue;
          const title = (a.innerText || a.textContent || "").trim();
          if (!title) continue;
          convos.push({ title, url: "https://gemini.google.com" + href });
        }
        return JSON.stringify(convos);
    })()"#;
        let raw = execute_js_for_profile(js, profile_filter, "safari_gemini_list_conversations")?;
        let items: Vec<SafariConversation> = serde_json::from_str(&raw)
            .map_err(|e| MacosError::Other(format!("failed to parse conversations: {e}")))?;
        Ok(items)
    })
}

pub fn gemini_read_conversation(
    url: &str,
    profile_filter: Option<&str>,
) -> Result<SafariAiResponseResult, MacosError> {
    with_safari_session(|| {
        let nav_js = format!(
            r#"(function() {{ window.location.href = "{url}"; return "true"; }})()"#,
            url = escape_js_string(url),
        );
        let _ = execute_js_for_profile(&nav_js, profile_filter, "safari_gemini_read_navigate")?;
        thread::sleep(Duration::from_millis(3000));

        let read_js = r#"(() => {
        const panels = document.querySelectorAll('.markdown.markdown-main-panel');
        let biggest = null;
        let maxLen = 0;
        for (const p of panels) {
          const len = (p.innerText || "").length;
          if (len > maxLen) { maxLen = len; biggest = p; }
        }
        if (!biggest || maxLen === 0) return JSON.stringify({ status: "empty", response: "" });
        return JSON.stringify({ status: "complete", response: (biggest.innerText || "").trim() });
    })()"#;
        let raw = execute_js_for_profile(read_js, profile_filter, "safari_gemini_read_content")?;
        let value: Value = serde_json::from_str(&raw)
            .map_err(|e| MacosError::Other(format!("failed to parse read result: {e}")))?;
        let status = value
            .get("status")
            .and_then(|v| v.as_str())
            .unwrap_or("error");
        let response = value.get("response").and_then(|v| v.as_str()).unwrap_or("");
        Ok(SafariAiResponseResult {
            provider: "gemini".to_string(),
            status: status.to_string(),
            response: response.to_string(),
            conversation_url: None,
        })
    })
}