feat: implement audio recording, global hotkey management, and system tray integration

This commit is contained in:
jahruz67
2026-04-09 10:41:18 -07:00
parent 2d36041f6a
commit cc644e3059
340 changed files with 181639 additions and 65 deletions
File diff suppressed because one or more lines are too long
+1
View File
@@ -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)}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=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}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)}
+189
View File
@@ -0,0 +1,189 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta content="width=device-width, initial-scale=1.0" name="viewport">
<title>wis-free-v3 Settings</title>
<script type="module" crossorigin src="/assets/index.33b5a8c3.js"></script>
<link rel="stylesheet" href="/assets/index.9197445a.css">
</head>
<body>
<div id="app" class="container">
<!-- Header -->
<div class="flex-between" style="margin-bottom: 30px;">
<h1 style="margin: 0;">wis-free-v3 Settings</h1>
<div style="color: var(--text-muted); font-size: 12px;">v1.0.0</div>
</div>
<div id="saveStatus" class="save-status">Settings Saved!</div>
<!-- Settings Form -->
<div id="settingsForm">
<!-- API Key -->
<div class="section">
<label>Groq API Key</label>
<div class="form-control">
<div class="flex-row">
<div class="input-wrapper">
<input type="password" id="apiKey" placeholder="gsk_...">
<button class="eye-btn" onclick="toggleApiKey()" id="eyeBtn">👁️</button>
</div>
<button onclick="saveApiKey()">Save</button>
</div>
</div>
<p style="font-size: 12px; color: var(--text-muted); margin-top: 8px;">
Get your free API key at <a href="https://console.groq.com/keys" target="_blank"
style="color: var(--primary);">console.groq.com/keys</a>
</p>
</div>
<!-- Shortcut -->
<div class="section">
<label>Shortcut (Hold to Record)</label>
<div class="form-control">
<div class="flex-row">
<div class="input-wrapper">
<input type="text" id="shortcutInput" placeholder="Click Record to set..." readonly>
</div>
<button onclick="recordShortcut()" id="recordBtn">Record</button>
</div>
</div>
<p style="font-size: 12px; color: var(--text-muted); margin-top: 8px;">
Click Record, then press your desired key combination (e.g., Ctrl+X)
</p>
</div>
<!-- Whisper Model -->
<div class="section">
<label>Whisper Model</label>
<select id="whisperModel" onchange="saveWhisperModel()" class="form-control">
<option value="whisper-large-v3-turbo">whisper-large-v3-turbo (Recommended)</option>
<option value="whisper-large-v3">whisper-large-v3</option>
</select>
</div>
<!-- Language -->
<div class="section">
<label>Transcription Language</label>
<select id="language" onchange="saveLanguage()" class="form-control">
<option value="en">English (en)</option>
<option value="es">Spanish (es)</option>
</select>
<p style="font-size: 11px; color: var(--text-muted); margin-top: 5px;">Specifying language reduces latency and improves accuracy.</p>
</div>
<!-- AI Model -->
<div class="section">
<label>AI Model (Text Refinement)</label>
<select id="aiModel" onchange="saveAiModel()" class="form-control">
<option value="None">None (Skip Refinement)</option>
<option value="openai/gpt-oss-120b-high">openai/gpt-oss-120b (high reasoning)</option>
<option value="openai/gpt-oss-120b">openai/gpt-oss-120b (low reasoning)</option>
<option value="openai/gpt-oss-20b-high">openai/gpt-oss-20b (high reasoning)</option>
<option value="openai/gpt-oss-20b">openai/gpt-oss-20b (low reasoning)</option>
<option value="llama-3.3-70b-versatile">llama-3.3-70b-versatile</option>
<option value="llama-3.1-8b-instant">llama-3.1-8b-instant (faster)</option>
</select>
</div>
<!-- Microphone -->
<div class="section">
<label>Microphone</label>
<select id="micDevice" onchange="saveMicDevice()" class="form-control">
<option value="default">System Default</option>
</select>
</div>
<!-- AI Prompt -->
<div class="section">
<label>AI Prompt (System Message)</label>
<textarea id="aiPrompt" rows="3" placeholder="Custom instructions for text refinement..."
class="form-control"></textarea>
<div style="text-align: right; margin-top: 10px;">
<button onclick="savePrompt()">Save Prompt</button>
</div>
</div>
<!-- Startup -->
<div class="section flex-between">
<span style="font-weight: 500;">Run on Windows Startup</span>
<label class="flex-row" style="margin: 0; cursor: pointer;">
<input type="checkbox" id="startupToggle" onchange="toggleStartup()" style="width: auto;">
<span>Enable</span>
</label>
</div>
<!-- History -->
<div class="section">
<div class="flex-between" style="margin-bottom: 10px;">
<label style="margin: 0;">Transcription History</label>
<button onclick="clearHistory()"
style="background: transparent; color: #f87171; padding: 0; font-size: 12px;">Clear</button>
</div>
<div id="historyList" class="history-list">
<div style="text-align: center; color: var(--text-muted); padding: 20px;">No history yet.</div>
</div>
</div>
<!-- Offline Mode -->
<div class="section"
style="margin-top: 30px; padding-top: 20px; border-top: 1px solid var(--border-color);">
<label>Offline Whisper</label>
<!-- Online/Offline Status -->
<div id="connectionStatus"
style="margin-bottom: 15px; padding: 10px; border-radius: 8px; text-align: center;">
Checking connection...
</div>
<!-- Whisper Status -->
<div id="whisperSection">
<div id="whisperNotInstalled">
<p style="font-size: 13px; color: var(--text-muted); margin-bottom: 15px;">
Install offline transcription for use without internet. Uses GPU acceleration if available.
</p>
<div style="margin-bottom: 15px;">
<label style="font-size: 12px;">Model Size:</label>
<select id="whisperModelSelect" style="margin-top: 5px;" class="form-control">
<option value="tiny">Tiny (75 MB) - Fastest</option>
<option value="base">Base (150 MB) - Fast</option>
<option value="small" selected>Small (500 MB) - Recommended</option>
<option value="medium">Medium (1.5 GB) - High Quality</option>
</select>
</div>
<button onclick="installWhisper()" id="installBtn"
style="background: linear-gradient(135deg, #6366f1, #8b5cf6); padding: 12px 24px; font-size: 14px; width: 100%;">
🚀 Install Offline Whisper
</button>
</div>
<div id="whisperInstalled" style="display: none;">
<div
style="background: rgba(34, 197, 94, 0.1); border: 1px solid rgba(34, 197, 94, 0.3); border-radius: 8px; padding: 15px; margin-bottom: 15px;">
<div style="color: #22c55e; font-weight: 500;">✓ Offline Whisper Installed</div>
<div style="font-size: 12px; color: var(--text-muted); margin-top: 5px;">
Model: <span id="installedModel">-</span>
</div>
</div>
<p style="font-size: 12px; color: var(--text-muted); margin-bottom: 15px;">
Select "Local - Whisper" in the Whisper Model dropdown above to use offline transcription.
</p>
<button onclick="uninstallWhisper()"
style="background: transparent; border: 1px solid #f87171; color: #f87171; padding: 10px 20px; font-size: 13px;">
🗑️ Uninstall Offline Whisper
</button>
</div>
</div>
</div>
</div>
</div>
</body>
</html>