Skip to content

Commit b8122b1

Browse files
committed
fix: GigaAM-only installs no longer prompt for a missing whisper model
A fresh install where the user downloads and activates only GigaAM kept showing the 'no model installed' prompt on every launch, because the check always looked for the whisper fallback file — and the sidecar refused to start without it. - sidecar: a missing whisper model is no longer fatal at startup; it is checked lazily on the first whisper request with a clear error. The warmup command accepts an engine field and preloads GigaAM when asked. - app: the missing-model prompt now fires only when no usable engine exists (whisper file absent AND GigaAM not active+installed), and warmup targets the active engine. windows: bump version to 0.7.1
1 parent 17193b1 commit b8122b1

6 files changed

Lines changed: 49 additions & 20 deletions

File tree

sidecar-rust/src/main.rs

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,12 @@ impl ModelHolder {
7474

7575
fn ensure_loaded(&mut self) -> Result<&WhisperContext> {
7676
if self.ctx.is_none() {
77+
if !self.model_path.exists() {
78+
return Err(anyhow!(
79+
"whisper model file not found at {} (set FASTWORD_MODEL or download one)",
80+
self.model_path.display()
81+
));
82+
}
7783
log(&format!("loading model {}", self.model_path.display()));
7884
let path_str = self
7985
.model_path
@@ -238,8 +244,17 @@ fn handle(req: Request, engines: &Arc<Engines>) -> Response {
238244
)),
239245
"warmup" => {
240246
let silence = vec![0.0f32; 16_000 / 2]; // 0.5s
241-
let mut h = engines.whisper.lock().unwrap();
242-
let _ = h.transcribe(&silence, None, 0.0, None)?;
247+
match req.engine.as_deref() {
248+
// Preload the engine that will actually serve requests —
249+
// a GigaAM-only install has no whisper model at all.
250+
Some("gigaam") => {
251+
let _ = engines.run_gigaam(&silence)?;
252+
}
253+
_ => {
254+
let mut h = engines.whisper.lock().unwrap();
255+
let _ = h.transcribe(&silence, None, 0.0, None)?;
256+
}
257+
}
243258
Ok(String::new())
244259
}
245260
"transcribe" => {
@@ -302,9 +317,11 @@ fn main() -> Result<()> {
302317
std::env::var("FASTWORD_MODEL")
303318
.unwrap_or_else(|_| String::from("models/ggml-large-v3-turbo-q5_0.bin")),
304319
);
320+
// Missing whisper model is not fatal: it is loaded lazily on the first
321+
// whisper request, and a GigaAM-only setup never makes one.
305322
if !model_path.exists() {
306-
return Err(anyhow!(
307-
"model file not found at {} (set FASTWORD_MODEL)",
323+
log(&format!(
324+
"whisper model not found at {} — whisper requests will fail until it exists",
308325
model_path.display()
309326
));
310327
}

windows/Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

windows/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "fastword-win"
3-
version = "0.7.0"
3+
version = "0.7.1"
44
edition = "2021"
55
description = "FastWord for Windows — local, private, push-to-talk dictation"
66
license = "MIT"

windows/installer/fastword.iss

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
; (CI builds it automatically; Inno Setup 6 is preinstalled on GitHub
88
; windows runners.)
99

10-
#define AppVersion "0.7.0"
10+
#define AppVersion "0.7.1"
1111

1212
[Setup]
1313
AppId={{1A8161D4-A910-4857-AD86-254EB4ED4690}

windows/src/main.rs

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -169,10 +169,16 @@ fn run_app() -> Result<()> {
169169
}
170170
}
171171

172-
// A missing model is not fatal — a fresh install has none. Start
173-
// without the sidecar and point the user at the Models tab instead.
172+
// Engine routing (GigaAM is selected in the Models tab and is
173+
// Russian-only, so its language hint is pinned like on macOS).
174+
let gigaam_active = cfg.uses_gigaam() && models::gigaam_installed();
175+
176+
// A missing whisper model is not fatal — a fresh install has none, and
177+
// a GigaAM-only install doesn't need one at all. Only when *no* usable
178+
// engine exists do we start without the sidecar and point the user at
179+
// the Models tab.
174180
let model_path = cfg.resolved_model_path();
175-
let model_missing = !model_path.exists();
181+
let model_missing = !model_path.exists() && !gigaam_active;
176182
if model_missing {
177183
logging::log(&format!("model not found at {} — starting without sidecar", model_path.display()));
178184
}
@@ -194,9 +200,7 @@ fn run_app() -> Result<()> {
194200
let client = if model_missing { None } else { Some(spawner.spawn()?) };
195201

196202
let (mut language, mut initial_prompt) = languages::resolve(&cfg.language);
197-
// GigaAM selected in the Models tab: route to the sherpa-onnx engine.
198-
// It's Russian-only, so the language hint is pinned like on macOS.
199-
let engine = if cfg.uses_gigaam() && models::gigaam_installed() {
203+
let engine = if gigaam_active {
200204
language = "ru".to_string();
201205
initial_prompt = None;
202206
Some("gigaam".to_string())
@@ -628,9 +632,9 @@ fn controller_loop(
628632
Err(e) => logging::log(&format!("sidecar info failed: {e:#}")),
629633
}
630634

631-
// Load the model up front so the first dictation isn't slow.
635+
// Load the active engine up front so the first dictation isn't slow.
632636
let t0 = std::time::Instant::now();
633-
match client.warmup() {
637+
match client.warmup(cfg.engine.as_deref()) {
634638
Ok(_) => logging::log(&format!("model warmed up in {:.1}s", t0.elapsed().as_secs_f32())),
635639
Err(e) => logging::log(&format!("warmup failed: {e:#}")),
636640
}

windows/src/sidecar.rs

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,10 @@ pub struct Response {
3838
}
3939

4040
impl Request {
41-
pub fn warmup(id: &str) -> Self {
42-
Self::bare(id, "warmup")
41+
pub fn warmup(id: &str, engine: Option<&str>) -> Self {
42+
let mut req = Self::bare(id, "warmup");
43+
req.engine = engine.map(str::to_string);
44+
req
4345
}
4446

4547
pub fn info(id: &str) -> Self {
@@ -215,9 +217,10 @@ impl SidecarClient {
215217
})
216218
}
217219

218-
pub fn warmup(&mut self) -> Result<String> {
220+
/// Preload the engine that will serve requests (None = whisper).
221+
pub fn warmup(&mut self, engine: Option<&str>) -> Result<String> {
219222
let id = self.take_id();
220-
self.roundtrip(&Request::warmup(&id))
223+
self.roundtrip(&Request::warmup(&id, engine))
221224
}
222225

223226
/// Version/backend line, e.g. "fastword-sidecar 0.2.0 backend=cpu".
@@ -297,11 +300,16 @@ mod tests {
297300

298301
#[test]
299302
fn warmup_request_wire_format() {
300-
let req = Request::warmup("42");
303+
let req = Request::warmup("42", None);
301304
assert_eq!(
302305
serde_json::to_string(&req).unwrap(),
303306
r#"{"id":"42","cmd":"warmup"}"#
304307
);
308+
let req = Request::warmup("43", Some("gigaam"));
309+
assert_eq!(
310+
serde_json::to_string(&req).unwrap(),
311+
r#"{"id":"43","cmd":"warmup","engine":"gigaam"}"#
312+
);
305313
}
306314

307315
#[test]

0 commit comments

Comments
 (0)