-
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathlaunch.html
More file actions
206 lines (191 loc) · 12.9 KB
/
Copy pathlaunch.html
File metadata and controls
206 lines (191 loc) · 12.9 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="theme-color" content="#101211">
<title>Scriptify · Set up speech</title>
<link rel="shortcut icon" href="web/favicon.ico" type="image/x-icon">
<link rel="apple-touch-icon" href="web/apple-touch-icon.png">
<link rel="stylesheet" href="web/vendor/bootstrap-icons/bootstrap-icons.min.css">
<link rel="stylesheet" href="web/fonts/fonts.css">
<link rel="stylesheet" href="style.css">
</head>
<body class="launch-page">
<div class="launch-shell">
<header class="launch-bar pywebview-drag-region">
<a class="launch-brand" href="#" aria-label="Scriptify home">
<span class="launch-brand-mark" aria-hidden="true"><i class="bi bi-pencil-fill"></i></span>
<span class="launch-brand-name">scriptify</span>
</a>
<div class="launch-bar-label">VOICE SETUP</div>
<div class="window-controls" aria-label="Window controls">
<button id="min" type="button" title="Minimize" aria-label="Minimize window"><i class="bi bi-dash" aria-hidden="true"></i></button>
<button id="max" type="button" title="Toggle fullscreen" aria-label="Toggle fullscreen"><i class="bi bi-fullscreen" aria-hidden="true"></i></button>
<button id="close" class="close-window" type="button" title="Close" aria-label="Close window"><i class="bi bi-x-lg" aria-hidden="true"></i></button>
</div>
</header>
<main class="launch-main">
<section class="launch-intro">
<div class="launch-overline"><span class="status-dot" aria-hidden="true"></span> PRIVATE, ON-DEVICE TRANSCRIPTION</div>
<h1>Choose your speech model</h1>
<p>Pick the right balance of speed and accuracy. Your quantized ONNX model downloads once, then runs locally every time you dictate.</p>
</section>
<div class="model-grid" id="modelGrid" role="radiogroup" aria-label="Whisper speech model">
<button class="model-card" type="button" role="radio" aria-checked="false" data-model="tiny.en" data-language="English only" data-size="~45 MB" data-description="The quickest option for short notes and clear speech." data-badge="FASTEST">
<span class="model-card-title-row"><span class="model-card-title"><span class="model-index">01</span><strong>Tiny</strong></span><span class="model-tag">FASTEST</span></span>
<span class="model-card-description">Quick capture · lowest footprint</span>
<span class="model-card-footer"><span>English only</span><span>~45 MB</span></span>
</button>
<button class="model-card" type="button" role="radio" aria-checked="false" data-model="base.en" data-language="English only" data-size="~80 MB" data-description="A compact option for everyday dictation." data-badge="LIGHTWEIGHT">
<span class="model-card-title-row"><span class="model-card-title"><span class="model-index">02</span><strong>Base</strong></span><span class="model-tag">LIGHTWEIGHT</span></span>
<span class="model-card-description">Everyday notes · small download</span>
<span class="model-card-footer"><span>English only</span><span>~80 MB</span></span>
</button>
<button class="model-card" type="button" role="radio" aria-checked="false" data-model="small.en" data-language="English only" data-size="~250 MB" data-description="A strong everyday balance of recognition quality and speed." data-badge="RECOMMENDED">
<span class="model-card-title-row"><span class="model-card-title"><span class="model-index">03</span><strong>Small</strong></span><span class="model-tag model-tag-accent">RECOMMENDED</span></span>
<span class="model-card-description">Balanced accuracy · responsive</span>
<span class="model-card-footer"><span>English only</span><span>~250 MB</span></span>
</button>
<button class="model-card" type="button" role="radio" aria-checked="false" data-model="medium.en" data-language="English only" data-size="~850 MB" data-description="More accurate on varied speech, with a larger download." data-badge="HIGH ACCURACY">
<span class="model-card-title-row"><span class="model-card-title"><span class="model-index">04</span><strong>Medium</strong></span><span class="model-tag">HIGH ACCURACY</span></span>
<span class="model-card-description">More detail · larger download</span>
<span class="model-card-footer"><span>English only</span><span>~850 MB</span></span>
</button>
<button class="model-card" type="button" role="radio" aria-checked="false" data-model="large" data-language="Multilingual" data-size="~2 GB" data-description="The highest-accuracy option, with support for many languages." data-badge="MAX ACCURACY">
<span class="model-card-title-row"><span class="model-card-title"><span class="model-index">05</span><strong>Large</strong></span><span class="model-tag">MAX ACCURACY</span></span>
<span class="model-card-description">Multilingual · best detail</span>
<span class="model-card-footer"><span>Multilingual</span><span>~2 GB</span></span>
</button>
<button class="model-card" type="button" role="radio" aria-checked="false" data-model="turbo" data-language="Multilingual" data-size="~1 GB" data-description="Fast multilingual transcription with strong recognition quality." data-badge="FAST MULTILINGUAL">
<span class="model-card-title-row"><span class="model-card-title"><span class="model-index">06</span><strong>Turbo</strong></span><span class="model-tag">FAST MULTILINGUAL</span></span>
<span class="model-card-description">Speed and accuracy · many languages</span>
<span class="model-card-footer"><span>Multilingual</span><span>~1 GB</span></span>
</button>
</div>
<div class="launch-detail" id="launchDetail" aria-live="polite" role="status">
<i class="bi bi-info-circle" aria-hidden="true"></i>
<span>Select a model to see its details.</span>
</div>
<footer class="launch-footer">
<p class="launch-note"><span class="launch-note-icon"><i class="bi bi-shield-lock" aria-hidden="true"></i></span><span>Model files are stored in <code>models/</code> beside Scriptify. Download sizes are approximate; model weights are separate from the app.</span></p>
<button class="launch-start disabled" id="startButton" type="button" disabled>
<i class="bi bi-arrow-down-circle" aria-hidden="true"></i><span>Download model</span><i class="bi bi-arrow-right launch-button-arrow" aria-hidden="true"></i>
</button>
</footer>
</main>
<div class="launch-progress" id="launchProgress" role="progressbar" aria-label="Model setup progress" aria-valuemin="0" aria-valuemax="100" aria-valuenow="0" aria-hidden="true">
<span id="launchProgressBar"></span>
</div>
</div>
<script>
const modelGrid = document.getElementById("modelGrid");
const modelCards = Array.from(modelGrid.querySelectorAll(".model-card"));
const startButton = document.getElementById("startButton");
const detail = document.getElementById("launchDetail");
const progress = document.getElementById("launchProgress");
const progressBar = document.getElementById("launchProgressBar");
let selectedModel = "";
let selectedLabel = "";
function setStatus(message, tone = "info") {
detail.replaceChildren();
const icon = document.createElement("i");
icon.className = tone === "error"
? "bi bi-exclamation-circle"
: tone === "done" ? "bi bi-check2-circle" : "bi bi-info-circle";
icon.setAttribute("aria-hidden", "true");
const text = document.createElement("span");
text.textContent = message;
detail.append(icon, text);
detail.classList.toggle("is-error", tone === "error");
detail.classList.toggle("is-done", tone === "done");
if (tone === "error") {
modelGrid.classList.remove("disabled");
startButton.classList.remove("disabled");
startButton.disabled = !selectedModel;
progress.classList.remove("indeterminate");
progress.setAttribute("aria-hidden", "true");
progress.setAttribute("aria-valuenow", "0");
progressBar.style.width = "0%";
}
if (tone === "done") {
progress.classList.remove("indeterminate");
progress.setAttribute("aria-valuenow", "100");
progressBar.style.width = "100%";
}
}
function setLoadingStatus(message, tone = "info") {
setStatus(message, tone);
}
function selectCard(card) {
if (modelGrid.classList.contains("disabled")) return;
modelCards.forEach((other) => {
const isSelected = other === card;
other.classList.toggle("selected", isSelected);
other.setAttribute("aria-checked", String(isSelected));
});
selectedModel = card.dataset.model;
selectedLabel = card.querySelector("strong").textContent;
startButton.disabled = false;
startButton.classList.remove("disabled");
setStatus(`${card.dataset.description} ${card.dataset.language} · ${card.dataset.size} download · quantized ONNX.`);
}
modelCards.forEach((card) => {
card.addEventListener("click", () => selectCard(card));
card.addEventListener("keydown", (event) => {
const direction = event.key === "ArrowRight" || event.key === "ArrowDown" ? 1
: event.key === "ArrowLeft" || event.key === "ArrowUp" ? -1 : 0;
if (!direction) return;
event.preventDefault();
const index = modelCards.indexOf(card);
const next = modelCards[(index + direction + modelCards.length) % modelCards.length];
next.focus();
selectCard(next);
});
});
startButton.addEventListener("click", () => {
if (!selectedModel || startButton.disabled) return;
modelGrid.classList.add("disabled");
startButton.classList.add("disabled");
startButton.disabled = true;
progress.classList.add("indeterminate");
progress.setAttribute("aria-hidden", "false");
progress.removeAttribute("aria-valuenow");
progressBar.style.width = "35%";
setStatus(`Preparing ${selectedLabel}. Download progress is shown as indeterminate while the model files are fetched.`);
if (window.pywebview && window.pywebview.api) {
window.pywebview.api.download_model(selectedModel);
} else {
setStatus("The desktop bridge is unavailable. Close and reopen Scriptify to try again.", "error");
}
});
// Retained for API/backward compatibility. Model byte progress is not
// available from the downloader, so this only reports explicit values.
function updateProgressBar(value) {
const numeric = Number(value);
if (!Number.isFinite(numeric)) return;
const percent = Math.max(0, Math.min(100, numeric <= 1 ? numeric * 100 : numeric));
progress.classList.remove("indeterminate");
progress.classList.add("is-visible");
progress.setAttribute("aria-hidden", "false");
progress.setAttribute("aria-valuenow", String(Math.round(percent)));
progressBar.style.width = `${percent}%`;
}
function displayText(message) {
if (message) setStatus(message);
}
function closeWindow() {
if (window.pywebview && window.pywebview.api) window.pywebview.api.closeWindow();
}
function minimizeWindow() {
if (window.pywebview && window.pywebview.api) window.pywebview.api.minimizeWindow();
}
function toggleFullscreen() {
if (window.pywebview && window.pywebview.api) window.pywebview.api.toggleFullscreen();
}
document.getElementById("close").addEventListener("click", closeWindow);
document.getElementById("min").addEventListener("click", minimizeWindow);
document.getElementById("max").addEventListener("click", toggleFullscreen);
</script>
</body>
</html>