diff --git a/app.go b/app.go
index 8b8757b..c31cec4 100644
--- a/app.go
+++ b/app.go
@@ -6,6 +6,7 @@ import (
"os"
"path/filepath"
"runtime"
+ "sync/atomic"
"strings"
"time"
@@ -31,6 +32,7 @@ type App struct {
config *config.Config
overlay platform.Overlay
recordingPath string
+ recording int32
isQuitting bool
wasMediaPlaying bool
whisperManager *whisper.Manager
@@ -82,8 +84,47 @@ func (a *App) startup(ctx context.Context) {
}
}()
+ // Accept explicit commands from helper invocations (Linux desktop shortcuts).
+ go func() {
+ for cmd := range secondInstanceCommand {
+ switch cmd {
+ case instanceCmdShow:
+ wailsruntime.WindowShow(ctx)
+ case instanceCmdStart:
+ a.StartRecording()
+ case instanceCmdStop:
+ a.StopRecording()
+ case instanceCmdToggle:
+ a.ToggleRecording()
+ default:
+ }
+ }
+ }()
+
// Start system tray in a goroutine
- go tray.Start(a)
+ if runtime.GOOS == "linux" {
+ tray.Start(a)
+ } else {
+ go tray.Start(a)
+ }
+
+ if initialAction != "" {
+ action := initialAction
+ initialAction = ""
+ go func() {
+ switch action {
+ case "show":
+ wailsruntime.WindowShow(ctx)
+ case "start":
+ a.StartRecording()
+ case "stop":
+ a.StopRecording()
+ case "toggle":
+ a.ToggleRecording()
+ default:
+ }
+ }()
+ }
}
// beforeClose is called when the window is about to close
@@ -103,8 +144,27 @@ func (a *App) Quit() {
wailsruntime.Quit(a.ctx)
}
+var lastToggle time.Time
+
+func (a *App) ToggleRecording() {
+ // Debounce toggle calls to prevent rapid firing from double-binds or Wayland glitches
+ if time.Since(lastToggle) < 500*time.Millisecond {
+ return
+ }
+ lastToggle = time.Now()
+
+ if atomic.LoadInt32(&a.recording) == 1 {
+ a.StopRecording()
+ return
+ }
+ a.StartRecording()
+}
+
// StartRecording starts the audio recording
func (a *App) StartRecording() {
+ if !atomic.CompareAndSwapInt32(&a.recording, 0, 1) {
+ return
+ }
logger.Info("StartRecording triggered")
// 1. Show overlay immediately (most visible feedback)
@@ -120,6 +180,7 @@ func (a *App) StartRecording() {
if a.overlay != nil {
a.overlay.Hide()
}
+ atomic.StoreInt32(&a.recording, 0)
return
}
@@ -141,6 +202,7 @@ func (a *App) StartRecording() {
if a.overlay != nil {
a.overlay.Hide()
}
+ atomic.StoreInt32(&a.recording, 0)
return
}
}
@@ -160,6 +222,9 @@ func (a *App) StartRecording() {
// StopRecording stops the audio recording and triggers transcription
func (a *App) StopRecording() {
+ if !atomic.CompareAndSwapInt32(&a.recording, 1, 0) {
+ return
+ }
logger.Info("StopRecording triggered")
// Resume media if it was playing before
@@ -169,12 +234,15 @@ func (a *App) StopRecording() {
}
if a.audioRecorder == nil {
+ atomic.StoreInt32(&a.recording, 0)
return
}
err := a.audioRecorder.Stop()
if err != nil {
logger.Error("Failed to stop recording: %v", err)
+ // Attempt to keep state consistent: if stop failed, we are likely still recording.
+ atomic.StoreInt32(&a.recording, 1)
return
}
@@ -329,7 +397,7 @@ func (a *App) SaveSettings(settings map[string]interface{}) string {
a.hotkeyListener.UpdateShortcut(val)
} else {
// Should not happen if app started correctly, but just in case
- a.hotkeyListener = hotkey.NewListener(val, a.StartRecording, a.StopRecording)
+ a.hotkeyListener = hotkey.NewListener(val, a.ToggleRecording, func() {})
a.hotkeyListener.Start()
}
}
@@ -480,8 +548,8 @@ func (a *App) startupHeadless() {
logger.Error("Failed to ensure desktop file: %v", err)
}
- // Initialize Hotkey Listener
- a.hotkeyListener = hotkey.NewListener(a.config.Shortcut, a.StartRecording, a.StopRecording)
+ // Initialize Hotkey Listener (toggle mode: keydown toggles, keyup ignored)
+ a.hotkeyListener = hotkey.NewListener(a.config.Shortcut, a.ToggleRecording, func() {})
a.hotkeyListener.Start()
logger.Info("Components initialized successfully!")
diff --git a/build/bin/wis-free-v3.exe b/build/bin/wis-free-v3
old mode 100644
new mode 100755
similarity index 63%
rename from build/bin/wis-free-v3.exe
rename to build/bin/wis-free-v3
index 534b0a4..58874e5
Binary files a/build/bin/wis-free-v3.exe and b/build/bin/wis-free-v3 differ
diff --git a/frontend/dist/assets/index-BNpC-7X0.js b/frontend/dist/assets/index-BNpC-7X0.js
deleted file mode 100644
index b5292de..0000000
--- a/frontend/dist/assets/index-BNpC-7X0.js
+++ /dev/null
@@ -1,3 +0,0 @@
-(function(){let e=document.createElement(`link`).relList;if(e&&e.supports&&e.supports(`modulepreload`))return;for(let e of document.querySelectorAll(`link[rel="modulepreload"]`))n(e);new MutationObserver(e=>{for(let t of e)if(t.type===`childList`)for(let e of t.addedNodes)e.tagName===`LINK`&&e.rel===`modulepreload`&&n(e)}).observe(document,{childList:!0,subtree:!0});function t(e){let t={};return e.integrity&&(t.integrity=e.integrity),e.referrerPolicy&&(t.referrerPolicy=e.referrerPolicy),e.crossOrigin===`use-credentials`?t.credentials=`include`:e.crossOrigin===`anonymous`?t.credentials=`omit`:t.credentials=`same-origin`,t}function n(e){if(e.ep)return;e.ep=!0;let n=t(e);fetch(e.href,n)}})();var e=!1;window.toggleApiKey=function(){let t=document.getElementById(`apiKey`),n=document.getElementById(`eyeBtn`);e=!e,t.type=e?`text`:`password`,n.textContent=e?`🙈`:`👁️`},window.recordShortcut=function(){let e=document.getElementById(`recordBtn`),t=document.getElementById(`shortcutInput`),n=e=>e.metaKey||e.key===`Meta`||e.key===`OS`||e.code===`MetaLeft`||e.code===`MetaRight`;e.textContent=`Recording...`,e.disabled=!0,t.value=``;let r=``,i=e=>{e.preventDefault(),e.stopPropagation();let i=[];e.ctrlKey&&i.push(`ctrl`),e.altKey&&i.push(`alt`),e.shiftKey&&i.push(`shift`),n(e)&&i.push(`win`);let a=e.key.toLowerCase();a===`control`&&(a=`ctrl`),a===`alt`&&(a=`alt`),a===`shift`&&(a=`shift`),(a===`meta`||a===`os`)&&(a=`win`),(e.code===`MetaLeft`||e.code===`MetaRight`)&&(a=`win`),a===` `&&(a=`space`);let s=new Set(i);s.add(a);let c=Array.from(s).filter(e=>e!==a);c.push(a),r=c.join(`+`),t.value=r,[`ctrl`,`alt`,`shift`,`win`].includes(a)||o()},a=e=>{e.preventDefault(),e.stopPropagation(),r&&(r.split(`+`).every(e=>[`ctrl`,`alt`,`shift`,`win`].includes(e))&&r.includes(`+`)?setTimeout(o,100):!e.ctrlKey&&!e.altKey&&!e.shiftKey&&!n(e)&&o())},o=()=>{if(!r){e.textContent=`Record`,e.disabled=!1,s();return}window.go.main.App.SaveSettings({shortcut:r}).then(t=>{t&&t.indexOf(`Invalid shortcut`)!==-1&&alert(t),e.textContent=`Record`,e.disabled=!1,s()})},s=()=>{window.removeEventListener(`keydown`,i,!0),window.removeEventListener(`keyup`,a,!0)};window.addEventListener(`keydown`,i,!0),window.addEventListener(`keyup`,a,!0)};function t(e){let t=document.getElementById(`historyList`);if(!e||e.length===0){t.innerHTML=`
No history yet.
`;return}t.innerHTML=``,[...e].reverse().forEach(e=>{let n=document.createElement(`div`);n.className=`history-item`;let r=document.createElement(`span`);r.className=`history-time`,r.textContent=new Date(e.timestamp).toLocaleString(void 0,{month:`short`,day:`numeric`,hour:`2-digit`,minute:`2-digit`});let i=document.createElement(`div`);i.textContent=e.text,n.appendChild(r),n.appendChild(i),t.appendChild(n)})}async function n(){try{t((await window.go.main.App.GetSettings()).history||[])}catch(e){console.error(`Failed to refresh history:`,e)}}async function r(){try{let e=await window.go.main.App.GetSettings();document.getElementById(`apiKey`).value=e.api_key||``,document.getElementById(`shortcutInput`).value=e.shortcut||`alt+z`,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;let n=e.app_version&&String(e.app_version).trim()||`dev`;document.getElementById(`appVersion`).textContent=n===`dev`?`dev`:`v`+n,t(e.history||[])}catch(e){console.error(`Failed to load settings:`,e)}}async function i(){try{let e=await window.go.main.App.GetMicrophones(),t=document.getElementById(`micDevice`);e.forEach(e=>{if(e.index!==-1){let n=document.createElement(`option`);n.value=e.index,n.textContent=e.name,t.appendChild(n)}})}catch(e){console.error(`Failed to load mics:`,e)}}window.saveApiKey=async function(){let e=document.getElementById(`apiKey`).value.trim();await window.go.main.App.SaveSettings({api_key:e}),a()};function a(e=`Settings Saved!`){let 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}),a()},window.saveAiModel=async function(){await window.go.main.App.SaveSettings({ai_model:document.getElementById(`aiModel`).value}),a()},window.saveLanguage=async function(){await window.go.main.App.SaveSettings({language:document.getElementById(`language`).value}),a(`Language Saved!`)},window.saveMicDevice=async function(){let e=document.getElementById(`micDevice`).value;await window.go.main.App.SaveSettings({microphone_device:e===`default`?null:parseInt(e)}),a()},window.savePrompt=async function(){await window.go.main.App.SaveSettings({ai_prompt:document.getElementById(`aiPrompt`).value.trim()}),a(`Prompt Saved!`)},window.toggleStartup=async function(){await window.go.main.App.ToggleStartup(document.getElementById(`startupToggle`).checked)},window.clearHistory=async function(){confirm(`Clear all history?`)&&(await window.go.main.App.ClearHistory(),t([]))};async function o(){try{let e=await window.go.main.App.CheckOnline(),t=document.getElementById(`connectionStatus`);if(e)t.innerHTML=`🟢 Online - Cloud transcription available`,t.style.background=`rgba(34, 197, 94, 0.1)`,t.style.color=`#22c55e`;else{t.innerHTML=`🔴 Offline - Install local Whisper for transcription`,t.style.background=`rgba(239, 68, 68, 0.1)`,t.style.color=`#ef4444`;let e=document.getElementById(`aiModel`);document.getElementById(`whisperModel`);for(let t of e.options)t.value!==`None`&&!t.value.startsWith(`local-`)&&(t.disabled=!0,t.text.includes(`(offline)`)||(t.text+=` (offline)`))}}catch(e){console.error(`Connection check failed:`,e)}}async function s(){try{let e=await window.go.main.App.GetWhisperInfo(),t=document.getElementById(`whisperModel`),n=t.querySelector(`option[value^="local-"]`);if(n&&n.remove(),e.installed){document.getElementById(`whisperNotInstalled`).style.display=`none`,document.getElementById(`whisperInstalled`).style.display=`block`,document.getElementById(`installedModel`).textContent=e.model+` model`;let n=document.createElement(`option`);n.value=`local-`+e.model,n.textContent=`🖥️ Local - `+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(){let e=document.getElementById(`whisperModelSelect`).value,t=document.getElementById(`installBtn`);t.disabled=!0,t.textContent=`Starting installation...`;try{let t=await window.go.main.App.InstallWhisper(e);alert(t+`
-
-A terminal window will open showing the installation progress. This may take several minutes depending on your internet speed.`),setTimeout(s,5e3)}catch(e){alert(`Installation failed: `+e)}t.disabled=!1,t.textContent=`🚀 Install Offline Whisper`},window.uninstallWhisper=async function(){if(confirm(`Are you sure you want to uninstall offline Whisper? This will delete the downloaded model.`))try{let e=await window.go.main.App.UninstallWhisper();alert(e),s()}catch(e){alert(`Uninstall failed: `+e)}};async function c(){try{if(navigator.userAgent.toLowerCase().includes(`linux`)){let e=document.getElementById(`offlineWhisperSection`);e&&(e.style.display=`none`);let t=document.getElementById(`startupLabel`);t&&(t.innerText=`Run on Linux OS Startup`)}}catch(e){console.error(`Failed to get environment:`,e)}if(await r(),await i(),await o(),await s(),window.runtime&&typeof window.runtime.EventsOn==`function`&&window.runtime.EventsOn(`history:updated`,function(){n()}),navigator.userAgent.toLowerCase().includes(`linux`)){let e=document.getElementById(`whisperModel`);e&&Array.from(e.options).forEach(e=>{e.value.startsWith(`local-`)&&e.remove()})}}c();
\ No newline at end of file
diff --git a/frontend/dist/assets/index-BsntdVbh.css b/frontend/dist/assets/index-BsntdVbh.css
deleted file mode 100644
index a74adec..0000000
--- a/frontend/dist/assets/index-BsntdVbh.css
+++ /dev/null
@@ -1 +0,0 @@
-:root{--bg-color:#0b0f1a;--card-bg:#1e293bb3;--input-bg:#33415580;--text-color:#f8fafc;--text-muted:#94a3b8;--primary-color:#3b82f6;--primary-hover:#2563eb;--accent-color:#6366f1;--border-color:#4b556366;--success-color:#10b981;--glass-border:#ffffff1a;--lightningcss-light: ;--lightningcss-dark:initial;color-scheme:dark}body{color:var(--text-color);letter-spacing:-.01em;background:radial-gradient(circle at 100% 0,#1e293b,#0b0f1a);min-height:100vh;margin:0;padding:30px;font-family:Inter,system-ui,-apple-system,sans-serif}.container{max-width:720px;margin:0 auto}h1{background:linear-gradient(135deg,#60a5fa,#a78bfa);-webkit-text-fill-color:transparent;-webkit-background-clip:text;background-clip:text;margin-bottom:30px;font-size:28px;font-weight:700}.section{background:var(--card-bg);-webkit-backdrop-filter:blur(12px);border:1px solid var(--glass-border);border-radius:16px;margin-bottom:24px;padding:24px;transition:transform .2s;box-shadow:0 10px 15px -3px #0003}.section:hover{border-color:#fff3}label{color:var(--text-muted);text-transform:uppercase;letter-spacing:.05em;margin-bottom:10px;font-size:13px;font-weight:600;display:block}input[type=checkbox]{accent-color:var(--primary-color);cursor:pointer;width:18px;height:18px}input[type=text],input[type=password],select,textarea{background:var(--input-bg);border:1px solid var(--border-color);color:#fff;box-sizing:border-box;border-radius:10px;width:100%;padding:12px 14px;font-size:14px;transition:all .2s}select{appearance:none;background-image:url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%2394a3b8' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3e%3cpolyline points='6 9 12 15 18 9'%3e%3c/polyline%3e%3c/svg%3e");background-position:right 14px center;background-repeat:no-repeat;background-size:16px;padding-right:40px}select option{background:var(--bg-color);color:#fff}textarea{resize:vertical;min-height:80px}input:focus,select:focus,textarea:focus{border-color:var(--primary-color);background:#334155cc;outline:none;box-shadow:0 0 0 3px #3b82f633}.form-control{width:100%;max-width:480px}.input-wrapper{flex:1;min-width:0;position:relative}.input-wrapper input{box-sizing:border-box;width:100%;padding-right:44px;display:block}.eye-btn{cursor:pointer;opacity:.5;justify-content:center;align-items:center;font-size:16px;line-height:1;transition:opacity .2s;display:flex;position:absolute;top:50%;right:8px;transform:translateY(-50%);box-shadow:none!important;background:0 0!important;border:none!important;padding:4px!important}.eye-btn:hover{opacity:1;transform:translateY(-50%)scale(1.1)}.save-status{background:var(--success-color);color:#fff;opacity:0;z-index:1000;border-radius:12px;padding:12px 24px;font-weight:600;transition:all .3s cubic-bezier(.4,0,.2,1);position:fixed;bottom:30px;right:30px;transform:translateY(100px);box-shadow:0 10px 15px -3px #0003}.save-status.show{opacity:1;transform:translateY(0)}button{background-color:var(--primary-color);color:#fff;cursor:pointer;white-space:nowrap;border:none;border-radius:10px;flex-shrink:0;padding:10px 20px;font-size:14px;font-weight:600;transition:all .2s cubic-bezier(.4,0,.2,1);box-shadow:0 4px 6px -1px #0003}button:hover{background-color:var(--primary-hover);transform:translateY(-1px);box-shadow:0 10px 15px -3px #0000004d}button:active{transform:translateY(0)}button[style*="background: transparent"]{opacity:.7;text-decoration:underline;box-shadow:none!important;background:0 0!important}button[style*="background: transparent"]:hover{opacity:1}.flex-row{flex-wrap:nowrap;align-items:center;gap:12px;display:flex}.flex-between{justify-content:space-between;align-items:center;display:flex}.history-list{max-height:250px;margin-top:10px;padding-right:5px;overflow-y:auto}.history-item{background:#ffffff08;border:1px solid #ffffff0d;border-radius:12px;margin-bottom:10px;padding:14px;transition:all .2s}.history-item:hover{background:#ffffff0f;transform:translate(2px)}.history-time{color:var(--primary-color);margin-bottom:6px;font-size:11px;font-weight:700;display:block}::-webkit-scrollbar{width:6px}::-webkit-scrollbar-track{background:0 0}::-webkit-scrollbar-thumb{background:var(--border-color);border-radius:10px}::-webkit-scrollbar-thumb:hover{background:var(--text-muted)}
diff --git a/frontend/dist/assets/index.c31e3052.js b/frontend/dist/assets/index.c31e3052.js
new file mode 100644
index 0000000..3464b02
--- /dev/null
+++ b/frontend/dist/assets/index.c31e3052.js
@@ -0,0 +1,3 @@
+(function(){const t=document.createElement("link").relList;if(t&&t.supports&&t.supports("modulepreload"))return;for(const n of document.querySelectorAll('link[rel="modulepreload"]'))i(n);new MutationObserver(n=>{for(const s of n)if(s.type==="childList")for(const r of s.addedNodes)r.tagName==="LINK"&&r.rel==="modulepreload"&&i(r)}).observe(document,{childList:!0,subtree:!0});function a(n){const s={};return n.integrity&&(s.integrity=n.integrity),n.referrerpolicy&&(s.referrerPolicy=n.referrerpolicy),n.crossorigin==="use-credentials"?s.credentials="include":n.crossorigin==="anonymous"?s.credentials="omit":s.credentials="same-origin",s}function i(n){if(n.ep)return;n.ep=!0;const s=a(n);fetch(n.href,s)}})();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"),a=o=>o.metaKey||o.key==="Meta"||o.key==="OS"||o.code==="MetaLeft"||o.code==="MetaRight";e.textContent="Recording...",e.disabled=!0,t.value="";let i="";const n=o=>{o.preventDefault(),o.stopPropagation();const c=[];o.ctrlKey&&c.push("ctrl"),o.altKey&&c.push("alt"),o.shiftKey&&c.push("shift"),a(o)&&c.push("win");let l=o.key.toLowerCase();l==="control"&&(l="ctrl"),l==="alt"&&(l="alt"),l==="shift"&&(l="shift"),(l==="meta"||l==="os")&&(l="win"),(o.code==="MetaLeft"||o.code==="MetaRight")&&(l="win"),l===" "&&(l="space");const y=new Set(c);y.add(l);const w=Array.from(y).filter(g=>g!==l);w.push(l),i=w.join("+"),t.value=i,["ctrl","alt","shift","win"].includes(l)||r()},s=o=>{o.preventDefault(),o.stopPropagation(),i&&(i.split("+").every(l=>["ctrl","alt","shift","win"].includes(l))&&i.includes("+")?setTimeout(r,100):!o.ctrlKey&&!o.altKey&&!o.shiftKey&&!a(o)&&r())},r=()=>{if(!i){e.textContent="Record",e.disabled=!1,f();return}window.go.main.App.SaveSettings({shortcut:i}).then(o=>{o&&o.indexOf("Invalid shortcut")!==-1&&alert(o),e.textContent="Record",e.disabled=!1,f()})},f=()=>{window.removeEventListener("keydown",n,!0),window.removeEventListener("keyup",s,!0)};window.addEventListener("keydown",n,!0),window.addEventListener("keyup",s,!0)};function m(e){const t=document.getElementById("historyList");if(!e||e.length===0){t.innerHTML='No history yet.
';return}t.innerHTML="",[...e].reverse().forEach(a=>{const i=document.createElement("div");i.className="history-item";const n=document.createElement("span");n.className="history-time",n.textContent=new Date(a.timestamp).toLocaleString(void 0,{month:"short",day:"numeric",hour:"2-digit",minute:"2-digit"});const s=document.createElement("div");s.textContent=a.text,i.appendChild(n),i.appendChild(s),t.appendChild(i)})}async function h(){try{const e=await window.go.main.App.GetSettings();m(e.history||[])}catch(e){console.error("Failed to refresh history:",e)}}async function v(){try{const e=await window.go.main.App.GetSettings();document.getElementById("apiKey").value=e.api_key||"",document.getElementById("shortcutInput").value=e.shortcut||"alt+z",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 t=e.app_version&&String(e.app_version).trim()||"dev";document.getElementById("appVersion").textContent=t==="dev"?"dev":"v"+t,m(e.history||[])}catch(e){console.error("Failed to load settings:",e)}}async function E(){try{const e=await window.go.main.App.GetMicrophones(),t=document.getElementById("micDevice");e.forEach(a=>{if(a.index!==-1){const i=document.createElement("option");i.value=a.index,i.textContent=a.name,t.appendChild(i)}})}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="Settings 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("Language Saved!")};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.toggleStartup=async function(){await window.go.main.App.ToggleStartup(document.getElementById("startupToggle").checked)};window.clearHistory=async function(){confirm("Clear all history?")&&(await window.go.main.App.ClearHistory(),m([]))};async function I(){try{const e=await window.go.main.App.CheckOnline(),t=document.getElementById("connectionStatus");if(e)t.innerHTML="\u{1F7E2} Online - Cloud transcription available",t.style.background="rgba(34, 197, 94, 0.1)",t.style.color="#22c55e";else{t.innerHTML="\u{1F534} Offline - Install local Whisper for transcription",t.style.background="rgba(239, 68, 68, 0.1)",t.style.color="#ef4444";const a=document.getElementById("aiModel"),i=document.getElementById("whisperModel");for(let n of a.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"),a=t.querySelector('option[value^="local-"]');if(a&&a.remove(),e.installed){document.getElementById("whisperNotInstalled").style.display="none",document.getElementById("whisperInstalled").style.display="block",document.getElementById("installedModel").textContent=e.model+" model";const i=document.createElement("option");i.value="local-"+e.model,i.textContent="\u{1F5A5}\uFE0F Local - "+e.model+" (offline)",i.style.fontWeight="bold",t.insertBefore(i,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 a=await window.go.main.App.InstallWhisper(e);alert(a+`
+
+A terminal window will open showing the installation progress. This may take several minutes depending on your internet speed.`),setTimeout(p,5e3)}catch(a){alert("Installation failed: "+a)}t.disabled=!1,t.textContent="\u{1F680} Install Offline Whisper"};window.uninstallWhisper=async function(){if(!!confirm("Are you sure you want to 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 a=document.getElementById("startupLabel");a&&(a.innerText="Run on Linux OS Startup")}}catch(e){console.error("Failed to get environment:",e)}if(await v(),await E(),await I(),await p(),window.runtime&&typeof window.runtime.EventsOn=="function"&&window.runtime.EventsOn("history:updated",function(){h()}),navigator.userAgent.toLowerCase().includes("linux")){const e=document.getElementById("whisperModel");e&&Array.from(e.options).forEach(t=>{t.value.startsWith("local-")&&t.remove()})}}S();
diff --git a/frontend/dist/assets/index.d8635aeb.css b/frontend/dist/assets/index.d8635aeb.css
new file mode 100644
index 0000000..31c34a4
--- /dev/null
+++ b/frontend/dist/assets/index.d8635aeb.css
@@ -0,0 +1 @@
+:root{--bg-color: #0b0f1a;--card-bg: rgba(30, 41, 59, .7);--input-bg: rgba(51, 65, 85, .5);--text-color: #f8fafc;--text-muted: #94a3b8;--primary-color: #3b82f6;--primary-hover: #2563eb;--accent-color: #6366f1;--border-color: rgba(75, 85, 99, .4);--success-color: #10b981;--glass-border: rgba(255, 255, 255, .1);color-scheme:dark}body{background:radial-gradient(circle at top right,#1e293b,#0b0f1a);color:var(--text-color);font-family:Inter,system-ui,-apple-system,sans-serif;margin:0;padding:30px;min-height:100vh;letter-spacing:-.01em}.container{max-width:720px;margin:0 auto}h1{font-size:28px;font-weight:700;background:linear-gradient(135deg,#60a5fa,#a78bfa);-webkit-background-clip:text;background-clip:text;-webkit-text-fill-color:transparent;margin-bottom:30px}.section{background:var(--card-bg);backdrop-filter:blur(12px);-webkit-backdrop-filter:blur(12px);padding:24px;border-radius:16px;margin-bottom:24px;border:1px solid var(--glass-border);box-shadow:0 10px 15px -3px #0003;transition:transform .2s ease}.section:hover{border-color:#fff3}label{display:block;color:var(--text-muted);font-size:13px;font-weight:600;text-transform:uppercase;letter-spacing:.05em;margin-bottom:10px}input[type=checkbox]{accent-color:var(--primary-color);width:18px;height:18px;cursor:pointer}input[type=text],input[type=password],select,textarea{width:100%;background:var(--input-bg);border:1px solid var(--border-color);color:#fff;padding:12px 14px;border-radius:10px;font-size:14px;transition:all .2s ease;box-sizing:border-box}select{appearance:none;-webkit-appearance:none;background-image:url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%2394a3b8' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3e%3cpolyline points='6 9 12 15 18 9'%3e%3c/polyline%3e%3c/svg%3e");background-repeat:no-repeat;background-position:right 14px center;background-size:16px;padding-right:40px}select option{background:var(--bg-color);color:#fff}textarea{resize:vertical;min-height:80px}input:focus,select:focus,textarea:focus{outline:none;border-color:var(--primary-color);background:rgba(51,65,85,.8);box-shadow:0 0 0 3px #3b82f633}.form-control{max-width:480px;width:100%}.input-wrapper{position:relative;flex:1;min-width:0}.input-wrapper input{width:100%;padding-right:44px;box-sizing:border-box;display:block}.eye-btn{position:absolute;right:8px;top:50%;transform:translateY(-50%);background:transparent!important;border:none!important;box-shadow:none!important;cursor:pointer;padding:4px!important;font-size:16px;opacity:.5;transition:opacity .2s;line-height:1;display:flex;align-items:center;justify-content:center}.eye-btn:hover{opacity:1;transform:translateY(-50%) scale(1.1)}.save-status{position:fixed;bottom:30px;right:30px;background:var(--success-color);color:#fff;padding:12px 24px;border-radius:12px;font-weight:600;box-shadow:0 10px 15px -3px #0003;transform:translateY(100px);opacity:0;transition:all .3s cubic-bezier(.4,0,.2,1);z-index:1000}.save-status.show{transform:translateY(0);opacity:1}button{background-color:var(--primary-color);color:#fff;border:none;padding:10px 20px;border-radius:10px;cursor:pointer;font-weight:600;font-size:14px;transition:all .2s cubic-bezier(.4,0,.2,1);box-shadow:0 4px 6px -1px #0003;white-space:nowrap;flex-shrink:0}button:hover{transform:translateY(-1px);box-shadow:0 10px 15px -3px #0000004d;background-color:var(--primary-hover)}button:active{transform:translateY(0)}button[style*="background: transparent"]{background:transparent!important;box-shadow:none!important;text-decoration:underline;opacity:.7}button[style*="background: transparent"]:hover{opacity:1}.flex-row{display:flex;gap:12px;align-items:center;flex-wrap:nowrap}.flex-between{display:flex;justify-content:space-between;align-items:center}.history-list{max-height:250px;overflow-y:auto;margin-top:10px;padding-right:5px}.history-item{background:rgba(255,255,255,.03);padding:14px;border-radius:12px;margin-bottom:10px;border:1px solid rgba(255,255,255,.05);transition:all .2s ease}.history-item:hover{background:rgba(255,255,255,.06);transform:translate(2px)}.history-time{color:var(--primary-color);font-size:11px;font-weight:700;margin-bottom:6px;display:block}::-webkit-scrollbar{width:6px}::-webkit-scrollbar-track{background:transparent}::-webkit-scrollbar-thumb{background:var(--border-color);border-radius:10px}::-webkit-scrollbar-thumb:hover{background:var(--text-muted)}
diff --git a/frontend/dist/index.html b/frontend/dist/index.html
index bbfc781..981065e 100644
--- a/frontend/dist/index.html
+++ b/frontend/dist/index.html
@@ -5,8 +5,9 @@
wis-free-v3 Settings
-
-
+
+
+
@@ -182,6 +183,7 @@
+