cueward-adapter-macos 0.3.0

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

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

use super::super::super::core::execute_js_for_profile;
use super::super::{SafariAiResponseResult, should_skip_gemini_response, wait_and_click_send};
use super::{
    build_gemini_click_send_js, build_gemini_fill_input_js, build_gemini_go_home_js,
    gemini_response_extract_js,
};

pub fn ensure_gemini_home(profile_filter: Option<&str>) -> Result<(), MacosError> {
    with_safari_session(|| {
        let _ = execute_js_for_profile(
            &build_gemini_go_home_js(),
            profile_filter,
            "safari_gemini_go_home",
        )?;
        thread::sleep(Duration::from_millis(2500));
        Ok(())
    })
}

pub fn send_gemini_prompt(
    prompt: &str,
    profile_filter: Option<&str>,
) -> Result<SafariAiResponseResult, MacosError> {
    with_safari_session(|| {
        let filled = execute_js_for_profile(
            &build_gemini_fill_input_js(prompt),
            profile_filter,
            "safari_gemini_prompt_fill",
        )?;
        if filled.trim() != "true" {
            return Err(MacosError::Other(format!(
                "failed to fill Gemini input: {filled}"
            )));
        }

        wait_and_click_send(
            &build_gemini_click_send_js(),
            profile_filter,
            "safari_gemini_wait_send",
        )?;

        let mut last_text = String::new();
        let mut stable_count = 0;
        let deadline = Instant::now() + Duration::from_secs(60);
        let response_js = gemini_response_extract_js();

        while Instant::now() < deadline {
            thread::sleep(Duration::from_secs(1));
            let text =
                execute_js_for_profile(&response_js, profile_filter, "safari_gemini_response")?;
            let trimmed = text.trim();
            if should_skip_gemini_response(trimmed, prompt) {
                continue;
            }

            if trimmed == last_text {
                stable_count += 1;
                if stable_count >= 2 {
                    return Ok(SafariAiResponseResult {
                        provider: "gemini".to_string(),
                        status: "complete".to_string(),
                        response: trimmed.to_string(),
                        conversation_url: None,
                    });
                }
            } else {
                last_text = trimmed.to_string();
                stable_count = 0;
            }
        }

        if !last_text.is_empty() {
            return Ok(SafariAiResponseResult {
                provider: "gemini".to_string(),
                status: "timeout".to_string(),
                response: last_text,
                conversation_url: None,
            });
        }

        Err(MacosError::Other(
            "timeout waiting for Gemini response".to_string(),
        ))
    })
}