feat: implement ydotool support for Linux with auto-paste functionality and add setup script

This commit is contained in:
jahruz67
2026-05-20 16:12:55 -07:00
parent 4131d77326
commit 9b08ed249a
8 changed files with 274 additions and 53 deletions
+4
View File
@@ -0,0 +1,4 @@
(function(){const t=document.createElement("link").relList;if(t&&t.supports&&t.supports("modulepreload"))return;for(const i of document.querySelectorAll('link[rel="modulepreload"]'))n(i);new MutationObserver(i=>{for(const l of i)if(l.type==="childList")for(const r of l.addedNodes)r.tagName==="LINK"&&r.rel==="modulepreload"&&n(r)}).observe(document,{childList:!0,subtree:!0});function o(i){const l={};return i.integrity&&(l.integrity=i.integrity),i.referrerpolicy&&(l.referrerPolicy=i.referrerpolicy),i.crossorigin==="use-credentials"?l.credentials="include":i.crossorigin==="anonymous"?l.credentials="omit":l.credentials="same-origin",l}function n(i){if(i.ep)return;i.ep=!0;const l=o(i);fetch(i.href,l)}})();let u=!1;window.toggleApiKey=function(){const e=document.getElementById("apiKey"),t=document.getElementById("eyeBtn");u=!u,e.type=u?"text":"password",t.textContent=u?"\u{1F648}":"\u{1F441}\uFE0F"};window.recordShortcut=function(){const e=document.getElementById("recordBtn"),t=document.getElementById("shortcutInput"),o=a=>a.metaKey||a.key==="Meta"||a.key==="OS"||a.code==="MetaLeft"||a.code==="MetaRight";e.textContent="Recording...",e.disabled=!0,t.value="";let n="";const i=a=>{a.preventDefault(),a.stopPropagation();const c=[];a.ctrlKey&&c.push("ctrl"),a.altKey&&c.push("alt"),a.shiftKey&&c.push("shift"),o(a)&&c.push("win");let s=a.key.toLowerCase();s==="control"&&(s="ctrl"),s==="alt"&&(s="alt"),s==="shift"&&(s="shift"),(s==="meta"||s==="os")&&(s="win"),(a.code==="MetaLeft"||a.code==="MetaRight")&&(s="win"),s===" "&&(s="space");const f=new Set(c);f.add(s);const g=Array.from(f).filter(w=>w!==s);g.push(s),n=g.join("+"),t.value=n,["ctrl","alt","shift","win"].includes(s)||r()},l=a=>{a.preventDefault(),a.stopPropagation(),n&&(n.split("+").every(s=>["ctrl","alt","shift","win"].includes(s))&&n.includes("+")?setTimeout(r,100):!a.ctrlKey&&!a.altKey&&!a.shiftKey&&!o(a)&&r())},r=()=>{if(!n){e.textContent="Record",e.disabled=!1,y();return}window.go.main.App.SaveSettings({shortcut:n}).then(a=>{a&&a.indexOf("Invalid shortcut")!==-1&&alert(a),e.textContent="Record",e.disabled=!1,y()})},y=()=>{window.removeEventListener("keydown",i,!0),window.removeEventListener("keyup",l,!0)};window.addEventListener("keydown",i,!0),window.addEventListener("keyup",l,!0)};function m(e){const t=document.getElementById("historyList");if(!e||e.length===0){t.innerHTML='<div style="text-align: center; color: var(--text-2); padding: 24px 0; font-size: 13px;">No history yet.</div>';return}t.innerHTML="",[...e].reverse().forEach(o=>{const n=document.createElement("div");n.className="history-item";const i=document.createElement("span");i.className="history-time",i.textContent=new Date(o.timestamp).toLocaleString(void 0,{month:"short",day:"numeric",hour:"2-digit",minute:"2-digit"});const l=document.createElement("div");l.textContent=o.text,n.appendChild(i),n.appendChild(l),t.appendChild(n)})}function h(e){const t=document.getElementById("linuxYdotoolStatus");if(!t||!e)return;const o=!!e.ready;t.style.display="block",t.style.background=o?"var(--green-dim)":"var(--red-dim)",t.style.color=o?"var(--green)":"var(--red)",t.style.border=o?"1px solid rgba(61, 186, 110, 0.25)":"1px solid rgba(224, 82, 82, 0.25)",t.innerHTML="";const n=document.createElement("div");n.style.fontWeight="600",n.textContent=o?"Automatic paste ready":"Automatic paste needs ydotool setup",t.appendChild(n);const i=document.createElement("div");if(i.style.marginTop="4px",i.textContent=e.message||"",t.appendChild(i),!o&&Array.isArray(e.setup_commands)&&e.setup_commands.length){const l=document.createElement("pre");l.style.whiteSpace="pre-wrap",l.style.margin="8px 0 0",l.style.color="inherit",l.textContent=e.setup_commands.join(`
`),t.appendChild(l)}}async function v(){try{const e=await window.go.main.App.GetSettings();m(e.history||[])}catch(e){console.error("Failed to refresh history:",e)}}async function E(){try{const e=await window.go.main.App.GetSettings();document.getElementById("apiKey").value=e.api_key||"";const t=document.getElementById("shortcutInput");if(t&&(t.value=e.shortcut||"alt+z"),e.linux_press_mode){const n=document.getElementById("linuxPressSection"),i=document.getElementById("linuxPressCommand");n&&(n.style.display="block"),i&&(i.value=e.linux_press_command||""),h(e.linux_ydotool_status)}document.getElementById("whisperModel").value=e.whisper_model||"whisper-large-v3-turbo",document.getElementById("aiModel").value=e.ai_model||"llama-3.3-70b-versatile",document.getElementById("aiPrompt").value=e.ai_prompt||"",document.getElementById("language").value=e.language||"en",document.getElementById("startupToggle").checked=e.startup||!1;const o=e.app_version&&String(e.app_version).trim()||"dev";document.getElementById("appVersion").textContent=o==="dev"?"dev":"v"+o,m(e.history||[])}catch(e){console.error("Failed to load settings:",e)}}async function I(){try{const e=await window.go.main.App.GetMicrophones(),t=document.getElementById("micDevice");e.forEach(o=>{if(o.index!==-1){const n=document.createElement("option");n.value=o.index,n.textContent=o.name,t.appendChild(n)}})}catch(e){console.error("Failed to load mics:",e)}}window.saveApiKey=async function(){const e=document.getElementById("apiKey").value.trim();await window.go.main.App.SaveSettings({api_key:e}),d()};function d(e="Saved"){const t=document.getElementById("saveStatus");t.textContent=e,t.classList.add("show"),setTimeout(()=>t.classList.remove("show"),2e3)}window.saveWhisperModel=async function(){await window.go.main.App.SaveSettings({whisper_model:document.getElementById("whisperModel").value}),d()};window.saveAiModel=async function(){await window.go.main.App.SaveSettings({ai_model:document.getElementById("aiModel").value}),d()};window.saveLanguage=async function(){await window.go.main.App.SaveSettings({language:document.getElementById("language").value}),d()};window.saveMicDevice=async function(){const e=document.getElementById("micDevice").value;await window.go.main.App.SaveSettings({microphone_device:e==="default"?null:parseInt(e)}),d()};window.savePrompt=async function(){await window.go.main.App.SaveSettings({ai_prompt:document.getElementById("aiPrompt").value.trim()}),d("Prompt saved")};window.copyLinuxPressCommand=async function(){const e=document.getElementById("linuxPressCommand");if(!e)return;const t=e.value||"";try{navigator.clipboard&&navigator.clipboard.writeText?await navigator.clipboard.writeText(t):(e.focus(),e.select(),document.execCommand("copy")),d("Command copied")}catch(o){console.error("Failed to copy Linux command:",o)}};window.toggleStartup=async function(){await window.go.main.App.ToggleStartup(document.getElementById("startupToggle").checked)};window.clearHistory=async function(){confirm("Clear all transcription history?")&&(await window.go.main.App.ClearHistory(),m([]))};async function x(){try{const e=await window.go.main.App.CheckOnline(),t=document.getElementById("connectionStatus");if(e)t.innerHTML="\u25CF Online",t.style.background="var(--green-dim)",t.style.color="var(--green)",t.style.border="1px solid rgba(61, 186, 110, 0.2)";else{t.innerHTML="\u25CF Offline \u2014 install local Whisper for transcription",t.style.background="var(--red-dim)",t.style.color="var(--red)",t.style.border="1px solid rgba(224, 82, 82, 0.2)";const o=document.getElementById("aiModel");for(let n of o.options)n.value!=="None"&&!n.value.startsWith("local-")&&(n.disabled=!0,n.text.includes("(offline)")||(n.text+=" (offline)"))}}catch(e){console.error("Connection check failed:",e)}}async function p(){try{const e=await window.go.main.App.GetWhisperInfo(),t=document.getElementById("whisperModel"),o=t.querySelector('option[value^="local-"]');if(o&&o.remove(),e.installed){document.getElementById("whisperNotInstalled").style.display="none",document.getElementById("whisperInstalled").style.display="block",document.getElementById("installedModel").textContent=e.model+" model";const n=document.createElement("option");n.value="local-"+e.model,n.textContent="\u2B21 Local \u2014 "+e.model+" (offline)",n.style.fontWeight="bold",t.insertBefore(n,t.firstChild)}else document.getElementById("whisperNotInstalled").style.display="block",document.getElementById("whisperInstalled").style.display="none"}catch(e){console.error("Whisper status check failed:",e)}}window.installWhisper=async function(){const e=document.getElementById("whisperModelSelect").value,t=document.getElementById("installBtn");t.disabled=!0,t.textContent="Starting installation...";try{const o=await window.go.main.App.InstallWhisper(e);alert(o+`
A terminal window will open showing the installation progress. This may take several minutes depending on your internet speed.`),setTimeout(p,5e3)}catch(o){alert("Installation failed: "+o)}t.disabled=!1,t.textContent="Install Offline Whisper"};window.uninstallWhisper=async function(){if(!!confirm("Uninstall offline Whisper? This will delete the downloaded model."))try{const e=await window.go.main.App.UninstallWhisper();alert(e),p()}catch(e){alert("Uninstall failed: "+e)}};async function S(){try{if(navigator.userAgent.toLowerCase().includes("linux")){const t=document.getElementById("offlineWhisperSection");t&&(t.style.display="none");const o=document.getElementById("startupLabel");o&&(o.innerText="Run on Startup")}}catch(e){console.error("Failed to get environment:",e)}if(await E(),await I(),await x(),await p(),window.runtime&&typeof window.runtime.EventsOn=="function"&&window.runtime.EventsOn("history:updated",function(){v()}),navigator.userAgent.toLowerCase().includes("linux")){const e=document.getElementById("whisperModel");e&&Array.from(e.options).forEach(t=>{t.value.startsWith("local-")&&t.remove()})}}S();
File diff suppressed because one or more lines are too long
+2 -1
View File
@@ -6,7 +6,7 @@
<meta content="width=device-width, initial-scale=1.0" name="viewport">
<title>Wisp Settings</title>
<script type="module" crossorigin src="/assets/index.3a064eef.js"></script>
<script type="module" crossorigin src="/assets/index.27c7f0d4.js"></script>
<link rel="stylesheet" href="/assets/index.6e77aa4d.css">
</head>
@@ -56,6 +56,7 @@
</div>
<p class="hint">GNOME: Settings -> Keyboard -> Custom Shortcuts -> Add a shortcut with the copied command.</p>
<p class="hint">KDE: System Settings -> Shortcuts -> Command/URL -> Add a shortcut with the copied command.</p>
<div id="linuxYdotoolStatus" style="display: none; margin-top: 12px; padding: 10px 12px; border-radius: 7px; font-size: 12px; line-height: 1.45;"></div>
</div>
<!-- Microphone -->
+33
View File
@@ -54,6 +54,7 @@
</div>
<p class="hint">GNOME: Settings -> Keyboard -> Custom Shortcuts -> Add a shortcut with the copied command.</p>
<p class="hint">KDE: System Settings -> Shortcuts -> Command/URL -> Add a shortcut with the copied command.</p>
<div id="linuxYdotoolStatus" style="display: none; margin-top: 12px; padding: 10px 12px; border-radius: 7px; font-size: 12px; line-height: 1.45;"></div>
</div>
<!-- Microphone -->
@@ -318,6 +319,37 @@
});
}
function renderLinuxYdotoolStatus(status) {
const el = document.getElementById('linuxYdotoolStatus');
if (!el || !status) return;
const ready = !!status.ready;
el.style.display = 'block';
el.style.background = ready ? 'var(--green-dim)' : 'var(--red-dim)';
el.style.color = ready ? 'var(--green)' : 'var(--red)';
el.style.border = ready ? '1px solid rgba(61, 186, 110, 0.25)' : '1px solid rgba(224, 82, 82, 0.25)';
el.innerHTML = '';
const title = document.createElement('div');
title.style.fontWeight = '600';
title.textContent = ready ? 'Automatic paste ready' : 'Automatic paste needs ydotool setup';
el.appendChild(title);
const message = document.createElement('div');
message.style.marginTop = '4px';
message.textContent = status.message || '';
el.appendChild(message);
if (!ready && Array.isArray(status.setup_commands) && status.setup_commands.length) {
const pre = document.createElement('pre');
pre.style.whiteSpace = 'pre-wrap';
pre.style.margin = '8px 0 0';
pre.style.color = 'inherit';
pre.textContent = status.setup_commands.join('\n');
el.appendChild(pre);
}
}
async function refreshHistoryFromBackend() {
try {
const settings = await window.go.main.App.GetSettings();
@@ -342,6 +374,7 @@
const cmdInput = document.getElementById('linuxPressCommand');
if (section) section.style.display = 'block';
if (cmdInput) cmdInput.value = settings.linux_press_command || '';
renderLinuxYdotoolStatus(settings.linux_ydotool_status);
}
document.getElementById('whisperModel').value = settings.whisper_model || 'whisper-large-v3-turbo';
document.getElementById('aiModel').value = settings.ai_model || 'llama-3.3-70b-versatile';