import { invoke } from "@tauri-apps/api/core"; import { listen } from "@tauri-apps/api/event"; import * as diff from "diff"; type RewriteMetrics = { duration_ms: number; input_length: number; model: string; success: boolean; error_category?: string | null; }; type RuntimeStatus = { hotkey_registered: boolean; background_mode: boolean; active_model: string; api_key_configured: boolean; last_error?: string | null; rewrite_attempts: number; rewrite_successes: number; rewrite_failures: number; last_metrics?: RewriteMetrics | null; }; function requireElement(id: string): T { const element = document.getElementById(id); if (!element) { throw new Error(`Missing required element: ${id}`); } return element as T; } const inputEl = requireElement("input-text"); const outputEl = requireElement("output-text"); const modelSelect = requireElement("model-select"); const modeSelect = requireElement("mode-select"); const rewriteBtn = requireElement("rewrite-btn"); const copyBtn = requireElement("copy-btn"); const toggleDiffBtn = requireElement("toggle-diff-btn"); const diffView = requireElement("diff-view"); const loadingMsg = requireElement("loading"); const settingsBtn = requireElement("settings-btn"); const settingsModal = requireElement("settings-modal"); const closeSettingsBtn = requireElement("close-settings-btn"); const saveSettingsBtn = requireElement("save-settings-btn"); const apiKeyInput = requireElement("api-key-input"); const runtimeStatusEl = requireElement("runtime-status"); const helperBanner = requireElement("helper-banner"); const helperBannerText = requireElement("helper-banner-text"); const hideToTrayBtn = requireElement("hide-to-tray-btn"); const dismissBannerBtn = requireElement("dismiss-banner-btn"); let showDiff = false; function renderRuntimeStatus(status: RuntimeStatus) { const metricParts: string[] = []; if (status.last_metrics) { metricParts.push(`${status.last_metrics.duration_ms} ms`); metricParts.push(`${status.last_metrics.input_length} chars`); metricParts.push(status.last_metrics.success ? "last run ok" : "last run failed"); if (status.last_metrics.error_category) { metricParts.push(status.last_metrics.error_category); } } const lines = [ `Hotkey: ${status.hotkey_registered ? "Ctrl + Alt + R active" : "not registered"}`, `Window: ${status.background_mode ? "hidden to tray" : "visible"}`, `Model: ${status.active_model}`, `API key: ${status.api_key_configured ? "configured" : "missing"}`, `Rewrites: ${status.rewrite_successes}/${status.rewrite_attempts} successful`, ]; if (metricParts.length > 0) { lines.push(`Last run: ${metricParts.join(" | ")}`); } if (status.last_error) { lines.push(`Last error: ${status.last_error}`); } runtimeStatusEl.textContent = lines.join(" | "); } async function refreshRuntimeStatus() { try { const status = await invoke("get_runtime_status"); renderRuntimeStatus(status); } catch (error) { console.error("Failed to load runtime status", error); } } function renderDiff(original: string, rewritten: string) { const diffResult = diff.diffWords(original, rewritten); diffView.innerHTML = ""; diffResult.forEach((part) => { const span = document.createElement("span"); span.textContent = part.value; if (part.added) { span.className = "diff-add"; } else if (part.removed) { span.className = "diff-del"; } diffView.appendChild(span); }); } function showBanner(message: string) { helperBannerText.textContent = message; helperBanner.classList.remove("hidden"); } function hideBanner() { helperBanner.classList.add("hidden"); } modelSelect.addEventListener("change", async () => { try { await invoke("set_active_model", { model: modelSelect.value }); await refreshRuntimeStatus(); } catch (error) { console.error("Failed to sync active model", error); } }); void invoke("set_active_model", { model: modelSelect.value }) .then(refreshRuntimeStatus) .catch((error) => { console.error("Failed to initialize active model", error); }); settingsBtn.addEventListener("click", () => { settingsModal.classList.remove("hidden"); }); closeSettingsBtn.addEventListener("click", () => { settingsModal.classList.add("hidden"); }); dismissBannerBtn.addEventListener("click", hideBanner); hideToTrayBtn.addEventListener("click", async () => { try { await invoke("hide_main_window"); hideBanner(); await refreshRuntimeStatus(); } catch (error) { console.error("Failed to hide main window", error); } }); saveSettingsBtn.addEventListener("click", async () => { const key = apiKeyInput.value.trim(); if (!key) return; try { saveSettingsBtn.disabled = true; saveSettingsBtn.textContent = "Saving..."; await invoke("save_api_key", { key }); settingsModal.classList.add("hidden"); apiKeyInput.value = ""; await refreshRuntimeStatus(); alert("API key saved using native platform protection."); } catch (err) { alert("Error saving API key: " + err); } finally { saveSettingsBtn.disabled = false; saveSettingsBtn.textContent = "Save Settings"; } }); async function checkStatus() { try { const isSet = await invoke("get_api_key_status"); if (!isSet) { settingsModal.classList.remove("hidden"); } } catch (error) { console.error("Status check failed", error); } } rewriteBtn.addEventListener("click", async () => { const text = inputEl.value; if (!text.trim()) return; loadingMsg.classList.remove("hidden"); outputEl.classList.add("hidden"); diffView.classList.add("hidden"); rewriteBtn.disabled = true; try { const mode = modeSelect.value; const model = modelSelect.value; const result = await invoke("rewrite_text", { text, modeStr: mode, modelStr: model, }); outputEl.value = result; renderDiff(text, result); if (showDiff) { diffView.classList.remove("hidden"); } else { outputEl.classList.remove("hidden"); } } catch (error) { console.error("Rewrite failed:", error); outputEl.value = `Error: ${error}`; outputEl.classList.remove("hidden"); } finally { loadingMsg.classList.add("hidden"); rewriteBtn.disabled = false; await refreshRuntimeStatus(); } }); toggleDiffBtn.addEventListener("click", () => { showDiff = !showDiff; if (showDiff) { toggleDiffBtn.textContent = "Show Text"; outputEl.classList.add("hidden"); diffView.classList.remove("hidden"); } else { toggleDiffBtn.textContent = "Show Diff"; diffView.classList.add("hidden"); outputEl.classList.remove("hidden"); } }); copyBtn.addEventListener("click", async () => { try { await navigator.clipboard.writeText(outputEl.value); const originalText = copyBtn.textContent; copyBtn.textContent = "Copied!"; setTimeout(() => { copyBtn.textContent = originalText; }, 2000); } catch (error) { console.error("Failed to copy", error); } }); void listen("tray-hint", (event) => { showBanner(event.payload); }); void listen("runtime-status", (event) => { renderRuntimeStatus(event.payload); }); void Promise.all([checkStatus(), refreshRuntimeStatus()]);