whisper ui

This commit is contained in:
galister 2026-07-03 18:53:11 +09:00
parent 6000986d14
commit d6713176c6
4 changed files with 102 additions and 55 deletions

View File

@ -28,7 +28,7 @@ use wlx_common::{
use crate::{ use crate::{
frontend::{Frontend, FrontendTask, FrontendTasks}, frontend::{Frontend, FrontendTask, FrontendTasks},
tab::{settings::macros::MacroParams, Tab, TabType}, tab::{Tab, TabType, settings::macros::MacroParams},
views::ViewUpdateParams, views::ViewUpdateParams,
}; };
@ -246,10 +246,11 @@ impl<T> Tab<T> for TabSettings<T> {
// Dropdown handling // Dropdown handling
if let TickResult::Action(name) = self.context_menu.tick(&mut frontend.layout, &mut self.state)? { if let TickResult::Action(name) = self.context_menu.tick(&mut frontend.layout, &mut self.state)? {
if name.starts_with(';') && let Some(tab) = self.current_tab.as_mut() { if name.starts_with(';')
&& let Some(tab) = self.current_tab.as_mut()
{
let config = frontend.interface.general_config(data); let config = frontend.interface.general_config(data);
tab.context_menu_custom(name, config, &mut frontend.layout, &mut self.state)?; tab.context_menu_custom(name, config, &mut frontend.layout, &mut self.state)?;
} else if let (Some(setting), Some(id), Some(value), Some(text), Some(translated)) = { } else if let (Some(setting), Some(id), Some(value), Some(text), Some(translated)) = {
let mut s = name.splitn(5, ';'); let mut s = name.splitn(5, ';');
(s.next(), s.next(), s.next(), s.next(), s.next()) (s.next(), s.next(), s.next(), s.next(), s.next())

View File

@ -1,18 +1,30 @@
use std::rc::Rc; use std::rc::Rc;
use wgui::{ use wgui::{
components::button::{ButtonClickEvent, ComponentButton}, globals::WguiGlobals, i18n::Translation, layout::WidgetID, log::LogErr, parser::{Fetchable, TemplateParams}, task::Tasks, widget::label::WidgetLabel, windowing::context_menu::{self} components::button::{ButtonClickEvent, ComponentButton},
globals::WguiGlobals,
i18n::Translation,
layout::WidgetID,
log::LogErr,
parser::{Fetchable, TemplateParams},
task::Tasks,
widget::label::WidgetLabel,
windowing::context_menu::{self},
}; };
use wlx_common::{async_executor::AsyncExecutor, config::GeneralConfig}; use wlx_common::{async_executor::AsyncExecutor, config::GeneralConfig};
use crate::{ use crate::{
frontend::FrontendTasks, frontend::FrontendTasks,
tab::settings::{ tab::settings::{
SettingType, SettingsMountParams, SettingsTab, TabNameEnum, Task as ParentTask, horiz_cell, macros::{MacroParams, options_category, options_checkbox, options_range_f32} SettingType, SettingsMountParams, SettingsTab, TabNameEnum, Task as ParentTask, horiz_cell,
macros::{MacroParams, options_category, options_checkbox, options_range_f32},
}, },
util::{ util::{
popup_manager::PopupHolder, popup_manager::PopupHolder,
whisper::{WHISPER_MODELS, WhisperModel, whisper_any_models_downloaded, whisper_delete_all_models, whisper_model_from_name, whisper_model_path}, whisper::{
WHISPER_MODELS, WhisperModel, whisper_any_models_downloaded, whisper_delete_all_models, whisper_model_from_name,
whisper_model_path,
},
}, },
views::{self, ViewUpdateParams}, views::{self, ViewUpdateParams},
}; };
@ -24,6 +36,7 @@ enum Task {
WhisperDownload(&'static WhisperModel), WhisperDownload(&'static WhisperModel),
WhisperDownloadDone, WhisperDownloadDone,
CloseDialog, CloseDialog,
ReloadTab,
} }
pub struct State { pub struct State {
@ -48,24 +61,32 @@ impl SettingsTab for State {
if !whisper_model_path(model.file_name).exists() { if !whisper_model_path(model.file_name).exists() {
// download failed, set to selection to none // download failed, set to selection to none
par.general_config.whisper_model = "".into(); par.general_config.whisper_model = "".into();
// re-init the whole tab // reload the tab
self.parent_tasks.push(ParentTask::SetTab(TabNameEnum::Features)); self.parent_tasks.push(ParentTask::SetTab(TabNameEnum::Features));
} }
} }
} }
Task::WhisperRemoveUnused => { Task::WhisperRemoveUnused => {
let _ = whisper_delete_all_models().log_err("could not remove whisper models"); let _ = whisper_delete_all_models().log_err("could not remove whisper models");
}, }
Task::WhisperDownload(model) => { Task::WhisperDownload(model) => {
self.pending_download = Some(model); self.pending_download = Some(model);
self.show_whisper_download_dialog(model, par.executor.clone()); self.show_whisper_download_dialog(model, par.executor.clone());
}, }
Task::WhisperDownloadDone => { Task::WhisperDownloadDone => {
// we're happy if let Some(model) = self.pending_download.take() {
}, par.general_config.whisper_model = model.file_name.into();
// reload tab so that the downloaded checkmarks get populated
self.parent_tasks.push(ParentTask::SetTab(TabNameEnum::Features));
}
}
Task::CloseDialog => { Task::CloseDialog => {
let close_dialog = self.popup_dialog.get_close_callback(par.layout); let close_dialog = self.popup_dialog.get_close_callback(par.layout);
close_dialog(); close_dialog();
}
Task::ReloadTab => {
self.parent_tasks.push(ParentTask::SetTab(TabNameEnum::Features));
} }
} }
} }
@ -84,19 +105,27 @@ impl SettingsTab for State {
let mut s = action.splitn(3, ';'); let mut s = action.splitn(3, ';');
(s.next(), s.next(), s.next()) (s.next(), s.next(), s.next())
} { } {
if !model_name.is_empty() && let Some(model) = whisper_model_from_name(model_name) if !model_name.is_empty()
{ && let Some(model) = whisper_model_from_name(model_name)
let mut common = layout.common(); {
let mut label = state.fetch_widget_as::<WidgetLabel>(common.state, &format!("{id}_value"))?; let mut common = layout.common();
label.set_text(&mut common, Translation::from_raw_text(model.display_name)); let mut label = state.fetch_widget_as::<WidgetLabel>(common.state, &format!("{id}_value"))?;
label.set_text(&mut common, Translation::from_raw_text(model.display_name));
config.whisper_model = action.as_ref().into();
config.whisper_model = model.file_name.into();
self.show_whisper_model_dialog_box_download(model)?; if !whisper_model_path(model.file_name).exists() {
} else if whisper_any_models_downloaded().unwrap_or_default() { self.show_whisper_model_dialog_box_download(model)?;
self.show_whisper_model_dialog_box_cleanup()?; }
} else {
config.whisper_model = "".into();
// re-init the whole tab
if whisper_any_models_downloaded().unwrap_or_default() {
self.show_whisper_model_dialog_box_cleanup()?;
} else {
self.parent_tasks.push(ParentTask::SetTab(TabNameEnum::Features));
}
}
} }
}
Ok(()) Ok(())
} }
@ -145,7 +174,7 @@ impl State {
}) })
} }
fn show_whisper_download_dialog(&mut self, model: &WhisperModel, executor: AsyncExecutor) { fn show_whisper_download_dialog(&mut self, model: &WhisperModel, executor: AsyncExecutor) {
views::download_file::mount_popup( views::download_file::mount_popup(
self.popup_download.clone(), self.popup_download.clone(),
self.frontend_tasks.clone(), self.frontend_tasks.clone(),
@ -174,24 +203,23 @@ impl State {
globals: self.globals.clone(), globals: self.globals.clone(),
message: Translation::from_translation_key("APP_SETTINGS.WHISPER.NEED_TO_DOWNLOAD_MODEL"), message: Translation::from_translation_key("APP_SETTINGS.WHISPER.NEED_TO_DOWNLOAD_MODEL"),
entries: vec![ entries: vec![
views::dialog_box::ButtonEntry { views::dialog_box::ButtonEntry {
content: Translation::from_translation_key("APP_SETTINGS.CANCEL"), content: Translation::from_translation_key("APP_SETTINGS.CANCEL"),
icon: "dashboard/close.svg", icon: "dashboard/close.svg",
action: ACTION_CANCEL, action: ACTION_CANCEL,
}, },
views::dialog_box::ButtonEntry { views::dialog_box::ButtonEntry {
content: Translation::from_translation_key("DOWNLOAD"), content: Translation::from_translation_key("DOWNLOAD"),
icon: "dashboard/download.svg", icon: "dashboard/download.svg",
action: ACTION_DOWNLOAD, action: ACTION_DOWNLOAD,
}, },
], ],
on_action_click: Box::new(move |action| match action { on_action_click: Box::new(move |action| match action {
ACTION_DOWNLOAD => { ACTION_DOWNLOAD => {
tasks.push(Task::WhisperDownload(model)); tasks.push(Task::WhisperDownload(model));
} }
ACTION_CANCEL => { ACTION_CANCEL => {
tasks.push(Task::CloseDialog); tasks.push(Task::CloseDialog);
// treat as failed download // treat as failed download
tasks.push(Task::WhisperDownloadClosed); tasks.push(Task::WhisperDownloadClosed);
} }
@ -216,23 +244,26 @@ impl State {
globals: self.globals.clone(), globals: self.globals.clone(),
message: Translation::from_translation_key("APP_SETTINGS.WHISPER.REMOVE_UNUSED_MODELS"), message: Translation::from_translation_key("APP_SETTINGS.WHISPER.REMOVE_UNUSED_MODELS"),
entries: vec![ entries: vec![
views::dialog_box::ButtonEntry { views::dialog_box::ButtonEntry {
content: Translation::from_translation_key("APP_SETTINGS.CANCEL"), content: Translation::from_translation_key("APP_SETTINGS.CANCEL"),
icon: "dashboard/close.svg", icon: "dashboard/close.svg",
action: ACTION_CANCEL, action: ACTION_CANCEL,
}, },
views::dialog_box::ButtonEntry { views::dialog_box::ButtonEntry {
content: Translation::from_translation_key("REMOVE"), content: Translation::from_translation_key("REMOVE"),
icon: "dashboard/trash.svg", icon: "dashboard/trash.svg",
action: ACTION_REMOVE, action: ACTION_REMOVE,
}, },
], ],
on_action_click: Box::new(move |action| match action { on_action_click: Box::new(move |action| match action {
ACTION_REMOVE => { ACTION_REMOVE => {
tasks.push(Task::WhisperRemoveUnused); tasks.push(Task::WhisperRemoveUnused);
tasks.push(Task::CloseDialog);
tasks.push(Task::ReloadTab);
} }
ACTION_CANCEL => { ACTION_CANCEL => {
tasks.push(Task::CloseDialog); tasks.push(Task::CloseDialog);
tasks.push(Task::ReloadTab);
} }
_ => unreachable!(), _ => unreachable!(),
}), }),
@ -249,6 +280,13 @@ fn whisper_models_dropdown(mp: &mut MacroParams, parent: WidgetID) -> anyhow::Re
let parent = horiz_cell(mp.layout, parent)?; let parent = horiz_cell(mp.layout, parent)?;
let mut downloaded_models = vec![];
for (idx, m) in WHISPER_MODELS.iter().enumerate() {
if whisper_model_path(m.file_name).exists() {
downloaded_models.push(idx);
}
}
let current_file = mp.config.whisper_model.as_ref(); let current_file = mp.config.whisper_model.as_ref();
let current_translation = whisper_model_from_name(current_file) let current_translation = whisper_model_from_name(current_file)
.map_or(Translation::from_translation_key("APP_SETTINGS.OPTION.NONE"), |x| { .map_or(Translation::from_translation_key("APP_SETTINGS.OPTION.NONE"), |x| {
@ -276,9 +314,14 @@ fn whisper_models_dropdown(mp: &mut MacroParams, parent: WidgetID) -> anyhow::Re
move |_common, e: ButtonClickEvent| { move |_common, e: ButtonClickEvent| {
let mut cells = WHISPER_MODELS let mut cells = WHISPER_MODELS
.iter() .iter()
.map(|item| context_menu::Cell { .enumerate()
.map(|(idx, item)| context_menu::Cell {
action_name: Some(format!(";{id};{}", item.file_name).into()), action_name: Some(format!(";{id};{}", item.file_name).into()),
title: Translation::from_raw_text(item.display_name), title: Translation::from_raw_text_string(format!(
"{}{}",
item.display_name,
if downloaded_models.contains(&idx) { "" } else { "" }
)),
tooltip: None, tooltip: None,
attribs: vec![], attribs: vec![],
}) })

View File

@ -161,7 +161,8 @@ async fn fetch_and_follow_redirects(
fn uri_try_from_str(s: &str) -> anyhow::Result<hyper::http::uri::PathAndQuery> { fn uri_try_from_str(s: &str) -> anyhow::Result<hyper::http::uri::PathAndQuery> {
use std::convert::TryInto; use std::convert::TryInto;
let uri: hyper::Uri = s.try_into().context("invalid URI")?; let uri: hyper::Uri = s.try_into().context("invalid URI")?;
uri.path_and_query() uri
.path_and_query()
.ok_or_else(|| anyhow::anyhow!("URI has no path and query component")) .ok_or_else(|| anyhow::anyhow!("URI has no path and query component"))
.cloned() .cloned()
} }
@ -183,7 +184,9 @@ async fn follow_single_redirect(
hyper::Uri::try_from(location_str).context("invalid redirect location")? hyper::Uri::try_from(location_str).context("invalid redirect location")?
} else { } else {
let mut parts = original_uri.into_parts(); let mut parts = original_uri.into_parts();
parts.path_and_query = uri_try_from_str(location_str).context("invalid redirect location")?.into(); parts.path_and_query = uri_try_from_str(location_str)
.context("invalid redirect location")?
.into();
hyper::Uri::from_parts(parts).context("failed to construct redirect URI")? hyper::Uri::from_parts(parts).context("failed to construct redirect URI")?
}; };

View File

@ -5,9 +5,9 @@ pub mod cache_dir;
pub mod common; pub mod common;
pub mod config; pub mod config;
pub mod config_io; pub mod config_io;
pub mod data_dir;
pub mod dash_interface; pub mod dash_interface;
pub mod dash_interface_emulated; pub mod dash_interface_emulated;
pub mod data_dir;
pub mod desktop_finder; pub mod desktop_finder;
mod handle; mod handle;
pub mod locale; pub mod locale;