A few optimizations on startup (#182)
* Use an optimal release profile... load managers & hotkeys first, add a splash, simplify app handler * removed splashscreen, hopefully app boots fast enough anyhow * No need to load transcription model on start now that its loaded on demand & cleanup * Make an unloaded model status * tiny modification to unloading * update comment --------- Co-authored-by: CJ Pais <cj@cjpais.com>
This commit is contained in:
parent
a286fe5143
commit
93215dc097
11 changed files with 153 additions and 129 deletions
|
|
@ -71,3 +71,9 @@ tauri-plugin-autostart = "2"
|
||||||
tauri-plugin-global-shortcut = "2"
|
tauri-plugin-global-shortcut = "2"
|
||||||
tauri-plugin-single-instance = "2.3.2"
|
tauri-plugin-single-instance = "2.3.2"
|
||||||
tauri-plugin-updater = "2"
|
tauri-plugin-updater = "2"
|
||||||
|
|
||||||
|
[profile.release]
|
||||||
|
lto = true
|
||||||
|
codegen-units = 1
|
||||||
|
strip = true
|
||||||
|
panic = "abort"
|
||||||
|
|
|
||||||
|
|
@ -49,10 +49,101 @@ fn show_main_window(app: &AppHandle) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
eprintln!("Main window not found");
|
eprintln!("Main window not found.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn initialize_core_logic(app_handle: &AppHandle) {
|
||||||
|
// First, initialize the managers
|
||||||
|
let recording_manager = Arc::new(
|
||||||
|
AudioRecordingManager::new(app_handle).expect("Failed to initialize recording manager"),
|
||||||
|
);
|
||||||
|
let model_manager =
|
||||||
|
Arc::new(ModelManager::new(app_handle).expect("Failed to initialize model manager"));
|
||||||
|
let transcription_manager = Arc::new(
|
||||||
|
TranscriptionManager::new(app_handle, model_manager.clone())
|
||||||
|
.expect("Failed to initialize transcription manager"),
|
||||||
|
);
|
||||||
|
let history_manager =
|
||||||
|
Arc::new(HistoryManager::new(app_handle).expect("Failed to initialize history manager"));
|
||||||
|
|
||||||
|
// Add managers to Tauri's managed state
|
||||||
|
app_handle.manage(recording_manager.clone());
|
||||||
|
app_handle.manage(model_manager.clone());
|
||||||
|
app_handle.manage(transcription_manager.clone());
|
||||||
|
app_handle.manage(history_manager.clone());
|
||||||
|
|
||||||
|
// Initialize the shortcuts
|
||||||
|
shortcut::init_shortcuts(app_handle);
|
||||||
|
|
||||||
|
// Apply macOS Accessory policy if starting hidden
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
{
|
||||||
|
let settings = settings::get_settings(app_handle);
|
||||||
|
if settings.start_hidden {
|
||||||
|
let _ = app_handle.set_activation_policy(tauri::ActivationPolicy::Accessory);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Get the current theme to set the appropriate initial icon
|
||||||
|
let initial_theme = tray::get_current_theme(app_handle);
|
||||||
|
|
||||||
|
// Choose the appropriate initial icon based on theme
|
||||||
|
let initial_icon_path = tray::get_icon_path(initial_theme, tray::TrayIconState::Idle);
|
||||||
|
|
||||||
|
let tray = TrayIconBuilder::new()
|
||||||
|
.icon(
|
||||||
|
Image::from_path(
|
||||||
|
app_handle
|
||||||
|
.path()
|
||||||
|
.resolve(initial_icon_path, tauri::path::BaseDirectory::Resource)
|
||||||
|
.unwrap(),
|
||||||
|
)
|
||||||
|
.unwrap(),
|
||||||
|
)
|
||||||
|
.show_menu_on_left_click(true)
|
||||||
|
.icon_as_template(true)
|
||||||
|
.on_menu_event(|app, event| match event.id.as_ref() {
|
||||||
|
"settings" => {
|
||||||
|
show_main_window(app);
|
||||||
|
}
|
||||||
|
"check_updates" => {
|
||||||
|
show_main_window(app);
|
||||||
|
let _ = app.emit("check-for-updates", ());
|
||||||
|
}
|
||||||
|
"cancel" => {
|
||||||
|
use crate::utils::cancel_current_operation;
|
||||||
|
|
||||||
|
// Use centralized cancellation that handles all operations
|
||||||
|
cancel_current_operation(app);
|
||||||
|
}
|
||||||
|
"quit" => {
|
||||||
|
app.exit(0);
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
})
|
||||||
|
.build(app_handle)
|
||||||
|
.unwrap();
|
||||||
|
app_handle.manage(tray);
|
||||||
|
|
||||||
|
// Initialize tray menu with idle state
|
||||||
|
utils::update_tray_menu(app_handle, &utils::TrayIconState::Idle);
|
||||||
|
|
||||||
|
// Get the autostart manager and configure based on user setting
|
||||||
|
let autostart_manager = app_handle.autolaunch();
|
||||||
|
let settings = settings::get_settings(&app_handle);
|
||||||
|
|
||||||
|
if settings.autostart_enabled {
|
||||||
|
// Enable autostart if user has opted in
|
||||||
|
let _ = autostart_manager.enable();
|
||||||
|
} else {
|
||||||
|
// Disable autostart if user has opted out
|
||||||
|
let _ = autostart_manager.disable();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the recording overlay window (hidden by default)
|
||||||
|
utils::create_recording_overlay(app_handle);
|
||||||
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
fn trigger_update_check(app: AppHandle) -> Result<(), String> {
|
fn trigger_update_check(app: AppHandle) -> Result<(), String> {
|
||||||
app.emit("check-for-updates", ())
|
app.emit("check-for-updates", ())
|
||||||
|
|
@ -91,97 +182,18 @@ pub fn run() {
|
||||||
))
|
))
|
||||||
.manage(Mutex::new(ShortcutToggleStates::default()))
|
.manage(Mutex::new(ShortcutToggleStates::default()))
|
||||||
.setup(move |app| {
|
.setup(move |app| {
|
||||||
// Apply macOS Accessory policy early if starting hidden
|
let settings = settings::get_settings(&app.handle());
|
||||||
#[cfg(target_os = "macos")]
|
let app_handle = app.handle().clone();
|
||||||
{
|
|
||||||
let settings = settings::get_settings(&app.handle());
|
initialize_core_logic(&app_handle);
|
||||||
if settings.start_hidden {
|
|
||||||
let _ = app.set_activation_policy(tauri::ActivationPolicy::Accessory);
|
// Show main window only if not starting hidden
|
||||||
|
if !settings.start_hidden {
|
||||||
|
if let Some(main_window) = app_handle.get_webview_window("main") {
|
||||||
|
main_window.show().unwrap();
|
||||||
|
main_window.set_focus().unwrap();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Get the current theme to set the appropriate initial icon
|
|
||||||
let initial_theme = tray::get_current_theme(&app.handle());
|
|
||||||
|
|
||||||
// Choose the appropriate initial icon based on theme
|
|
||||||
let initial_icon_path = tray::get_icon_path(initial_theme, tray::TrayIconState::Idle);
|
|
||||||
|
|
||||||
let tray = TrayIconBuilder::new()
|
|
||||||
.icon(Image::from_path(app.path().resolve(
|
|
||||||
initial_icon_path,
|
|
||||||
tauri::path::BaseDirectory::Resource,
|
|
||||||
)?)?)
|
|
||||||
.show_menu_on_left_click(true)
|
|
||||||
.icon_as_template(true)
|
|
||||||
.on_menu_event(|app, event| match event.id.as_ref() {
|
|
||||||
"settings" => {
|
|
||||||
show_main_window(app);
|
|
||||||
}
|
|
||||||
"check_updates" => {
|
|
||||||
show_main_window(app);
|
|
||||||
let _ = app.emit("check-for-updates", ());
|
|
||||||
}
|
|
||||||
"cancel" => {
|
|
||||||
use crate::utils::cancel_current_operation;
|
|
||||||
|
|
||||||
// Use centralized cancellation that handles all operations
|
|
||||||
cancel_current_operation(app);
|
|
||||||
}
|
|
||||||
"quit" => {
|
|
||||||
app.exit(0);
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
})
|
|
||||||
.build(app)?;
|
|
||||||
app.manage(tray);
|
|
||||||
|
|
||||||
// Initialize tray menu with idle state
|
|
||||||
utils::update_tray_menu(&app.handle(), &utils::TrayIconState::Idle);
|
|
||||||
|
|
||||||
// Get the autostart manager and configure based on user setting
|
|
||||||
let autostart_manager = app.autolaunch();
|
|
||||||
let settings = settings::get_settings(&app.handle());
|
|
||||||
|
|
||||||
if settings.autostart_enabled {
|
|
||||||
// Enable autostart if user has opted in
|
|
||||||
let _ = autostart_manager.enable();
|
|
||||||
} else {
|
|
||||||
// Disable autostart if user has opted out
|
|
||||||
let _ = autostart_manager.disable();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Window is configured to start hidden to avoid flicker.
|
|
||||||
// If user didn't choose Start Hidden, show it now.
|
|
||||||
let settings = settings::get_settings(&app.handle());
|
|
||||||
if settings.start_hidden {
|
|
||||||
if let Some(main_window) = app.get_webview_window("main") {
|
|
||||||
let _ = main_window.hide();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
show_main_window(&app.handle());
|
|
||||||
}
|
|
||||||
|
|
||||||
let recording_manager = Arc::new(
|
|
||||||
AudioRecordingManager::new(app).expect("Failed to initialize recording manager"),
|
|
||||||
);
|
|
||||||
let model_manager =
|
|
||||||
Arc::new(ModelManager::new(&app).expect("Failed to initialize model manager"));
|
|
||||||
let transcription_manager = Arc::new(
|
|
||||||
TranscriptionManager::new(&app, model_manager.clone())
|
|
||||||
.expect("Failed to initialize transcription manager"),
|
|
||||||
);
|
|
||||||
let history_manager =
|
|
||||||
Arc::new(HistoryManager::new(&app).expect("Failed to initialize history manager"));
|
|
||||||
|
|
||||||
// Add managers to Tauri's managed state
|
|
||||||
app.manage(recording_manager.clone());
|
|
||||||
app.manage(model_manager.clone());
|
|
||||||
app.manage(transcription_manager.clone());
|
|
||||||
app.manage(history_manager.clone());
|
|
||||||
|
|
||||||
// Create the recording overlay window (hidden by default)
|
|
||||||
utils::create_recording_overlay(&app.handle());
|
|
||||||
|
|
||||||
shortcut::init_shortcuts(app);
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ use crate::utils;
|
||||||
use log::{debug, info};
|
use log::{debug, info};
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
use std::time::Instant;
|
use std::time::Instant;
|
||||||
use tauri::{App, Manager};
|
use tauri::Manager;
|
||||||
|
|
||||||
const WHISPER_SAMPLE_RATE: usize = 16000;
|
const WHISPER_SAMPLE_RATE: usize = 16000;
|
||||||
|
|
||||||
|
|
@ -63,8 +63,8 @@ pub struct AudioRecordingManager {
|
||||||
impl AudioRecordingManager {
|
impl AudioRecordingManager {
|
||||||
/* ---------- construction ------------------------------------------------ */
|
/* ---------- construction ------------------------------------------------ */
|
||||||
|
|
||||||
pub fn new(app: &App) -> Result<Self, anyhow::Error> {
|
pub fn new(app: &tauri::AppHandle) -> Result<Self, anyhow::Error> {
|
||||||
let settings = get_settings(&app.handle());
|
let settings = get_settings(app);
|
||||||
let mode = if settings.always_on_microphone {
|
let mode = if settings.always_on_microphone {
|
||||||
MicrophoneMode::AlwaysOn
|
MicrophoneMode::AlwaysOn
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -74,7 +74,7 @@ impl AudioRecordingManager {
|
||||||
let manager = Self {
|
let manager = Self {
|
||||||
state: Arc::new(Mutex::new(RecordingState::Idle)),
|
state: Arc::new(Mutex::new(RecordingState::Idle)),
|
||||||
mode: Arc::new(Mutex::new(mode.clone())),
|
mode: Arc::new(Mutex::new(mode.clone())),
|
||||||
app_handle: app.handle().clone(),
|
app_handle: app.clone(),
|
||||||
|
|
||||||
recorder: Arc::new(Mutex::new(None)),
|
recorder: Arc::new(Mutex::new(None)),
|
||||||
is_open: Arc::new(Mutex::new(false)),
|
is_open: Arc::new(Mutex::new(false)),
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ use rusqlite::{params, Connection, OptionalExtension};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use tauri::{App, AppHandle, Emitter, Manager};
|
use tauri::{AppHandle, Emitter, Manager};
|
||||||
use tauri_plugin_sql::{Migration, MigrationKind};
|
use tauri_plugin_sql::{Migration, MigrationKind};
|
||||||
|
|
||||||
use crate::audio_toolkit::save_wav_file;
|
use crate::audio_toolkit::save_wav_file;
|
||||||
|
|
@ -27,11 +27,9 @@ pub struct HistoryManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl HistoryManager {
|
impl HistoryManager {
|
||||||
pub fn new(app: &App) -> Result<Self> {
|
pub fn new(app_handle: &AppHandle) -> Result<Self> {
|
||||||
let app_handle = app.app_handle().clone();
|
|
||||||
|
|
||||||
// Create recordings directory in app data dir
|
// Create recordings directory in app data dir
|
||||||
let app_data_dir = app.path().app_data_dir()?;
|
let app_data_dir = app_handle.path().app_data_dir()?;
|
||||||
let recordings_dir = app_data_dir.join("recordings");
|
let recordings_dir = app_data_dir.join("recordings");
|
||||||
let db_path = app_data_dir.join("history.db");
|
let db_path = app_data_dir.join("history.db");
|
||||||
|
|
||||||
|
|
@ -42,7 +40,7 @@ impl HistoryManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
let manager = Self {
|
let manager = Self {
|
||||||
app_handle,
|
app_handle: app_handle.clone(),
|
||||||
recordings_dir,
|
recordings_dir,
|
||||||
db_path,
|
db_path,
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ use std::io::Write;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::sync::Mutex;
|
use std::sync::Mutex;
|
||||||
use tar::Archive;
|
use tar::Archive;
|
||||||
use tauri::{App, AppHandle, Emitter, Manager};
|
use tauri::{AppHandle, Emitter, Manager};
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
pub enum EngineType {
|
pub enum EngineType {
|
||||||
|
|
@ -48,11 +48,9 @@ pub struct ModelManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ModelManager {
|
impl ModelManager {
|
||||||
pub fn new(app: &App) -> Result<Self> {
|
pub fn new(app_handle: &AppHandle) -> Result<Self> {
|
||||||
let app_handle = app.app_handle().clone();
|
|
||||||
|
|
||||||
// Create models directory in app data
|
// Create models directory in app data
|
||||||
let models_dir = app
|
let models_dir = app_handle
|
||||||
.path()
|
.path()
|
||||||
.app_data_dir()
|
.app_data_dir()
|
||||||
.map_err(|e| anyhow::anyhow!("Failed to get app data dir: {}", e))?
|
.map_err(|e| anyhow::anyhow!("Failed to get app data dir: {}", e))?
|
||||||
|
|
@ -152,7 +150,7 @@ impl ModelManager {
|
||||||
);
|
);
|
||||||
|
|
||||||
let manager = Self {
|
let manager = Self {
|
||||||
app_handle,
|
app_handle: app_handle.clone(),
|
||||||
models_dir,
|
models_dir,
|
||||||
available_models: Mutex::new(available_models),
|
available_models: Mutex::new(available_models),
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
|
||||||
use std::sync::{Arc, Condvar, Mutex};
|
use std::sync::{Arc, Condvar, Mutex};
|
||||||
use std::thread;
|
use std::thread;
|
||||||
use std::time::{Duration, SystemTime};
|
use std::time::{Duration, SystemTime};
|
||||||
use tauri::{App, AppHandle, Emitter, Manager};
|
use tauri::{AppHandle, Emitter};
|
||||||
use transcribe_rs::{
|
use transcribe_rs::{
|
||||||
engines::{
|
engines::{
|
||||||
parakeet::{
|
parakeet::{
|
||||||
|
|
@ -46,9 +46,7 @@ pub struct TranscriptionManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TranscriptionManager {
|
impl TranscriptionManager {
|
||||||
pub fn new(app: &App, model_manager: Arc<ModelManager>) -> Result<Self> {
|
pub fn new(app_handle: &AppHandle, model_manager: Arc<ModelManager>) -> Result<Self> {
|
||||||
let app_handle = app.app_handle().clone();
|
|
||||||
|
|
||||||
let manager = Self {
|
let manager = Self {
|
||||||
engine: Arc::new(Mutex::new(None)),
|
engine: Arc::new(Mutex::new(None)),
|
||||||
model_manager,
|
model_manager,
|
||||||
|
|
@ -105,7 +103,7 @@ impl TranscriptionManager {
|
||||||
let _ = app_handle_cloned.emit(
|
let _ = app_handle_cloned.emit(
|
||||||
"model-state-changed",
|
"model-state-changed",
|
||||||
ModelStateEvent {
|
ModelStateEvent {
|
||||||
event_type: "unloaded_due_to_idle".to_string(),
|
event_type: "unloaded".to_string(),
|
||||||
model_id: None,
|
model_id: None,
|
||||||
model_name: None,
|
model_name: None,
|
||||||
error: None,
|
error: None,
|
||||||
|
|
@ -126,10 +124,6 @@ impl TranscriptionManager {
|
||||||
*manager.watcher_handle.lock().unwrap() = Some(handle);
|
*manager.watcher_handle.lock().unwrap() = Some(handle);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try to load the default model from settings, but don't fail if no models are available
|
|
||||||
let settings = get_settings(&app_handle);
|
|
||||||
let _ = manager.load_model(&settings.selected_model);
|
|
||||||
|
|
||||||
Ok(manager)
|
Ok(manager)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -161,7 +155,7 @@ impl TranscriptionManager {
|
||||||
let _ = self.app_handle.emit(
|
let _ = self.app_handle.emit(
|
||||||
"model-state-changed",
|
"model-state-changed",
|
||||||
ModelStateEvent {
|
ModelStateEvent {
|
||||||
event_type: "unloaded_manually".to_string(),
|
event_type: "unloaded".to_string(),
|
||||||
model_id: None,
|
model_id: None,
|
||||||
model_name: None,
|
model_name: None,
|
||||||
error: None,
|
error: None,
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use tauri::{App, AppHandle};
|
use tauri::AppHandle;
|
||||||
use tauri_plugin_store::StoreExt;
|
use tauri_plugin_store::StoreExt;
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||||
|
|
@ -247,7 +247,7 @@ pub fn get_default_settings() -> AppSettings {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn load_or_create_app_settings(app: &App) -> AppSettings {
|
pub fn load_or_create_app_settings(app: &AppHandle) -> AppSettings {
|
||||||
// Initialize store
|
// Initialize store
|
||||||
let store = app
|
let store = app
|
||||||
.store(SETTINGS_STORE_PATH)
|
.store(SETTINGS_STORE_PATH)
|
||||||
|
|
|
||||||
|
|
@ -1,21 +1,19 @@
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use tauri::{App, AppHandle, Emitter, Manager};
|
use tauri::{AppHandle, Emitter, Manager};
|
||||||
use tauri_plugin_autostart::ManagerExt;
|
use tauri_plugin_autostart::ManagerExt;
|
||||||
use tauri_plugin_global_shortcut::GlobalShortcutExt;
|
use tauri_plugin_global_shortcut::{GlobalShortcutExt, Shortcut, ShortcutState};
|
||||||
use tauri_plugin_global_shortcut::{Shortcut, ShortcutState};
|
|
||||||
|
|
||||||
use crate::actions::ACTION_MAP;
|
use crate::actions::ACTION_MAP;
|
||||||
use crate::settings::ShortcutBinding;
|
use crate::settings::ShortcutBinding;
|
||||||
use crate::settings::{self, get_settings, OverlayPosition, PasteMethod, SoundTheme};
|
use crate::settings::{self, get_settings, OverlayPosition, PasteMethod, SoundTheme};
|
||||||
use crate::ManagedToggleState;
|
use crate::ManagedToggleState;
|
||||||
|
|
||||||
pub fn init_shortcuts(app: &App) {
|
pub fn init_shortcuts(app: &AppHandle) {
|
||||||
let settings = settings::load_or_create_app_settings(app);
|
let settings = settings::load_or_create_app_settings(app);
|
||||||
|
|
||||||
// Register shortcuts with the bindings from settings
|
// Register shortcuts with the bindings from settings
|
||||||
for (_id, binding) in settings.bindings {
|
for (_id, binding) in settings.bindings {
|
||||||
// Pass app.handle() which is &AppHandle
|
if let Err(e) = _register_shortcut(app, binding) {
|
||||||
if let Err(e) = _register_shortcut(app.handle(), binding) {
|
|
||||||
eprintln!("Failed to register shortcut {} during init: {}", _id, e);
|
eprintln!("Failed to register shortcut {} during init: {}", _id, e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,13 +13,15 @@
|
||||||
"macOSPrivateApi": true,
|
"macOSPrivateApi": true,
|
||||||
"windows": [
|
"windows": [
|
||||||
{
|
{
|
||||||
|
"label": "main",
|
||||||
"title": "Handy",
|
"title": "Handy",
|
||||||
"width": 680,
|
"width": 680,
|
||||||
"height": 510,
|
"height": 510,
|
||||||
"minWidth": 680,
|
"minWidth": 680,
|
||||||
"minHeight": 510,
|
"minHeight": 510,
|
||||||
"resizable": true,
|
"resizable": true,
|
||||||
"maximizable": false
|
"maximizable": false,
|
||||||
|
"visible": false
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"security": {
|
"security": {
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,14 @@ interface DownloadProgress {
|
||||||
percentage: number;
|
percentage: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
type ModelStatus = "ready" | "loading" | "downloading" | "extracting" | "error" | "none";
|
type ModelStatus =
|
||||||
|
| "ready"
|
||||||
|
| "loading"
|
||||||
|
| "downloading"
|
||||||
|
| "extracting"
|
||||||
|
| "error"
|
||||||
|
| "unloaded"
|
||||||
|
| "none";
|
||||||
|
|
||||||
interface DownloadStats {
|
interface DownloadStats {
|
||||||
startTime: number;
|
startTime: number;
|
||||||
|
|
@ -36,7 +43,7 @@ interface ModelSelectorProps {
|
||||||
const ModelSelector: React.FC<ModelSelectorProps> = ({ onError }) => {
|
const ModelSelector: React.FC<ModelSelectorProps> = ({ onError }) => {
|
||||||
const [models, setModels] = useState<ModelInfo[]>([]);
|
const [models, setModels] = useState<ModelInfo[]>([]);
|
||||||
const [currentModelId, setCurrentModelId] = useState<string>("");
|
const [currentModelId, setCurrentModelId] = useState<string>("");
|
||||||
const [modelStatus, setModelStatus] = useState<ModelStatus>("loading");
|
const [modelStatus, setModelStatus] = useState<ModelStatus>("unloaded");
|
||||||
const [modelError, setModelError] = useState<string | null>(null);
|
const [modelError, setModelError] = useState<string | null>(null);
|
||||||
const [modelDownloadProgress, setModelDownloadProgress] = useState<
|
const [modelDownloadProgress, setModelDownloadProgress] = useState<
|
||||||
Map<string, DownloadProgress>
|
Map<string, DownloadProgress>
|
||||||
|
|
@ -75,6 +82,10 @@ const ModelSelector: React.FC<ModelSelectorProps> = ({ onError }) => {
|
||||||
setModelStatus("error");
|
setModelStatus("error");
|
||||||
setModelError(error || "Failed to load model");
|
setModelError(error || "Failed to load model");
|
||||||
break;
|
break;
|
||||||
|
case "unloaded":
|
||||||
|
setModelStatus("unloaded");
|
||||||
|
setModelError(null);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
@ -247,7 +258,7 @@ const ModelSelector: React.FC<ModelSelectorProps> = ({ onError }) => {
|
||||||
if (transcriptionStatus === current) {
|
if (transcriptionStatus === current) {
|
||||||
setModelStatus("ready");
|
setModelStatus("ready");
|
||||||
} else {
|
} else {
|
||||||
setModelStatus("loading");
|
setModelStatus("unloaded");
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
setModelStatus("none");
|
setModelStatus("none");
|
||||||
|
|
@ -324,10 +335,12 @@ const ModelSelector: React.FC<ModelSelectorProps> = ({ onError }) => {
|
||||||
return currentModel ? `Extracting ${currentModel.name}...` : "Extracting...";
|
return currentModel ? `Extracting ${currentModel.name}...` : "Extracting...";
|
||||||
case "error":
|
case "error":
|
||||||
return modelError || "Model Error";
|
return modelError || "Model Error";
|
||||||
|
case "unloaded":
|
||||||
|
return currentModel?.name || "Model Unloaded";
|
||||||
case "none":
|
case "none":
|
||||||
return "No Model - Download Required";
|
return "No Model - Download Required";
|
||||||
default:
|
default:
|
||||||
return currentModel?.name || "Select Model";
|
return currentModel?.name || "Model Unloaded";
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ type ModelStatus =
|
||||||
| "downloading"
|
| "downloading"
|
||||||
| "extracting"
|
| "extracting"
|
||||||
| "error"
|
| "error"
|
||||||
|
| "unloaded"
|
||||||
| "none";
|
| "none";
|
||||||
|
|
||||||
interface ModelStatusButtonProps {
|
interface ModelStatusButtonProps {
|
||||||
|
|
@ -35,6 +36,8 @@ const ModelStatusButton: React.FC<ModelStatusButtonProps> = ({
|
||||||
return "bg-orange-400 animate-pulse";
|
return "bg-orange-400 animate-pulse";
|
||||||
case "error":
|
case "error":
|
||||||
return "bg-red-400";
|
return "bg-red-400";
|
||||||
|
case "unloaded":
|
||||||
|
return "bg-mid-gray/60";
|
||||||
case "none":
|
case "none":
|
||||||
return "bg-red-400";
|
return "bg-red-400";
|
||||||
default:
|
default:
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue