Skip to content

Commit 5f1a66f

Browse files
committed
chore: 发布 0.0.25\n\n- 托盘图标改为内嵌加载,移出目录仍保持自定义图标\n- 中文输出兼容增强:启用 Unicode 注入路径\n- 模型配置与重置确认弹窗,首次运行注册确认\n- 管理员权限改为构建特性 admin,可按需启用\n- 配置窗口布局修复,按钮不再被遮挡
1 parent 2d27ba9 commit 5f1a66f

File tree

6 files changed

+104
-15
lines changed

6 files changed

+104
-15
lines changed

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.

Cargo.toml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
[package]
22
name = "voice2type"
3-
version = "0.0.24"
3+
version = "0.0.25"
44
edition = "2021"
55
authors = ["guchang233"]
66

7+
[features]
8+
admin = []
9+
710
[dependencies]
811
# 异步运行时
912
tokio = { version = "1.36", features = ["full"] }

build.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@ fn main() {
44
if cfg!(target_os = "windows") {
55
let mut res = winres::WindowsResource::new();
66
res.set_icon("icon.ico");
7-
let profile = std::env::var("PROFILE").unwrap_or_else(|_| "debug".to_string());
8-
let level = if profile == "release" { "requireAdministrator" } else { "asInvoker" };
7+
let is_admin = std::env::var("CARGO_FEATURE_ADMIN").is_ok();
8+
let level = if is_admin { "requireAdministrator" } else { "asInvoker" };
99
let manifest = format!(r#"
1010
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
1111
<dependency>

src/config.rs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,11 @@ pub struct ConfigManager {
1212
}
1313

1414
#[derive(Debug, Clone, Serialize, Deserialize)]
15+
#[serde(default)]
1516
pub struct AppConfig {
1617
pub api_key: String,
18+
pub api_url: String,
19+
pub model_name: String,
1720
pub allow_emoji: bool,
1821
pub allow_punctuation: bool,
1922
pub show_log: bool,
@@ -24,6 +27,8 @@ impl Default for AppConfig {
2427
fn default() -> Self {
2528
Self {
2629
api_key: String::new(),
30+
api_url: "https://api.siliconflow.cn/v1/audio/transcriptions".to_string(),
31+
model_name: "FunAudioLLM/SenseVoiceSmall".to_string(),
2732
allow_emoji: true, // 默认开启
2833
allow_punctuation: true, // 默认开启
2934
show_log: false, // 默认关闭
@@ -78,6 +83,22 @@ impl ConfigManager {
7883
self.config.lock().unwrap().api_key = key;
7984
}
8085

86+
pub fn get_api_url(&self) -> String {
87+
self.config.lock().unwrap().api_url.clone()
88+
}
89+
90+
pub fn set_api_url(&self, url: String) {
91+
self.config.lock().unwrap().api_url = url;
92+
}
93+
94+
pub fn get_model_name(&self) -> String {
95+
self.config.lock().unwrap().model_name.clone()
96+
}
97+
98+
pub fn set_model_name(&self, model: String) {
99+
self.config.lock().unwrap().model_name = model;
100+
}
101+
81102
pub fn allow_emoji(&self) -> bool {
82103
self.config.lock().unwrap().allow_emoji
83104
}
@@ -110,6 +131,13 @@ impl ConfigManager {
110131
self.config.lock().unwrap().language = lang;
111132
}
112133

134+
pub fn reset_ai_config(&self) {
135+
let mut cfg = self.config.lock().unwrap();
136+
cfg.api_key = AppConfig::default().api_key;
137+
cfg.api_url = AppConfig::default().api_url;
138+
cfg.model_name = AppConfig::default().model_name;
139+
}
140+
113141
pub fn save(&self) -> anyhow::Result<()> {
114142
let config = self.config.lock().unwrap();
115143
let json = serde_json::to_string_pretty(&*config)?;

src/gui.rs

Lines changed: 67 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ pub struct Voice2TypeApp {
3434
#[nwg_events(MousePressLeftUp: [Voice2TypeApp::show_menu], OnContextMenu: [Voice2TypeApp::show_menu])]
3535
pub tray: nwg::TrayNotification,
3636

37-
#[nwg_resource(source_file: Some("icon.ico"))]
37+
#[nwg_resource(source_bin: Some(include_bytes!("../icon.ico")))]
3838
pub icon: nwg::Icon,
3939

4040
// 托盘菜单
@@ -67,11 +67,11 @@ pub struct Voice2TypeApp {
6767
#[nwg_control(parent: tray_menu, text: "配置")]
6868
pub config_menu: nwg::Menu,
6969

70-
#[nwg_control(parent: config_menu, text: "API Key...")]
70+
#[nwg_control(parent: config_menu, text: "模型配置")]
7171
#[nwg_events(OnMenuItemSelected: [Voice2TypeApp::show_config_window])]
7272
pub config_api_item: nwg::MenuItem,
7373

74-
#[nwg_control(parent: config_menu, text: "编辑配置")]
74+
#[nwg_control(parent: config_menu, text: "打开配置目录")]
7575
#[nwg_events(OnMenuItemSelected: [Voice2TypeApp::open_config_dir])]
7676
pub config_file_item: nwg::MenuItem,
7777

@@ -99,7 +99,7 @@ pub struct Voice2TypeApp {
9999
pub quit_item: nwg::MenuItem,
100100

101101
// --- 配置窗口 ---
102-
#[nwg_control(size: (400, 150), position: (300, 300), title: "Voice2Type API 配置", flags: "WINDOW", icon: Some(&data.icon))]
102+
#[nwg_control(size: (480, 240), position: (300, 300), title: "模型配置", flags: "WINDOW", icon: Some(&data.icon))]
103103
#[nwg_events(OnWindowClose: [Voice2TypeApp::hide_config_window])]
104104
pub config_window: nwg::Window, // 初始时我们希望它隐藏,在 init 中处理
105105

@@ -114,10 +114,32 @@ pub struct Voice2TypeApp {
114114
#[nwg_layout_item(layout: config_layout, row: 0, col: 1, col_span: 2)]
115115
pub api_input: nwg::TextInput,
116116

117-
#[nwg_control(parent: config_window, text: "保存", position: (290, 70), size: (90, 30))]
117+
#[nwg_control(parent: config_window, text: "API URL:")]
118+
#[nwg_layout_item(layout: config_layout, row: 1, col: 0)]
119+
pub url_label: nwg::Label,
120+
121+
#[nwg_control(parent: config_window, text: "")] // 在 init 中设置
122+
#[nwg_layout_item(layout: config_layout, row: 1, col: 1, col_span: 2)]
123+
pub url_input: nwg::TextInput,
124+
125+
#[nwg_control(parent: config_window, text: "模型:")]
126+
#[nwg_layout_item(layout: config_layout, row: 2, col: 0)]
127+
pub model_label: nwg::Label,
128+
129+
#[nwg_control(parent: config_window, text: "")] // 在 init 中设置
130+
#[nwg_layout_item(layout: config_layout, row: 2, col: 1, col_span: 2)]
131+
pub model_input: nwg::TextInput,
132+
133+
#[nwg_control(parent: config_window, text: "保存")]
134+
#[nwg_layout_item(layout: config_layout, row: 3, col: 2)]
118135
#[nwg_events(OnButtonClick: [Voice2TypeApp::save_config])]
119136
pub save_btn: nwg::Button,
120137

138+
#[nwg_control(parent: config_window, text: "重置")]
139+
#[nwg_layout_item(layout: config_layout, row: 3, col: 1)]
140+
#[nwg_events(OnButtonClick: [Voice2TypeApp::reset_ai_config])]
141+
pub reset_btn: nwg::Button,
142+
121143
// 状态
122144
pub config_manager: RefCell<Option<Arc<ConfigManager>>>,
123145
}
@@ -160,12 +182,22 @@ impl Voice2TypeApp {
160182

161183
// 设置输入框文本
162184
app.api_input.set_text(&config_manager.get_api_key());
185+
app.url_input.set_text(&config_manager.get_api_url());
186+
app.model_input.set_text(&config_manager.get_model_name());
163187

164188
// 检查 API Key 是否为空 (首次运行)
165189
if config_manager.get_api_key().is_empty() {
166-
let msg = "哎呀,还没检测到 API Key 呢!\n\n为了能听懂您说话,咱得去整一个 SiliconFlow 的 API Key。\n\n点击“确定”后,我会帮您打开配置窗口,顺便再帮您打开注册页面。\n去注册个账号,白嫖一个免费的 Key 填进来呗?";
167-
nwg::simple_message("温馨提示", msg);
168-
let _ = open::that("https://cloud.siliconflow.cn/account/ak");
190+
#[cfg(target_os = "windows")]
191+
unsafe {
192+
use windows::Win32::UI::WindowsAndMessaging::{MessageBoxW, MB_YESNO, MB_ICONINFORMATION, IDYES};
193+
use windows::core::PCWSTR;
194+
let title = "温馨提示\0".encode_utf16().collect::<Vec<u16>>();
195+
let msg = "没检测到 API Key。\n是否前往注册页面获取?\0".encode_utf16().collect::<Vec<u16>>();
196+
let ret = MessageBoxW(None, PCWSTR(msg.as_ptr()), PCWSTR(title.as_ptr()), MB_YESNO | MB_ICONINFORMATION);
197+
if ret == IDYES {
198+
let _ = open::that("https://cloud.siliconflow.cn/account/ak");
199+
}
200+
}
169201
app.config_window.set_visible(true);
170202
}
171203

@@ -315,14 +347,40 @@ impl Voice2TypeApp {
315347

316348
fn save_config(&self) {
317349
let key = self.api_input.text();
350+
let url = self.url_input.text();
351+
let model = self.model_input.text();
318352
if let Some(mgr) = &*self.config_manager.borrow() {
319353
mgr.set_api_key(key);
354+
mgr.set_api_url(url);
355+
mgr.set_model_name(model);
320356
let _ = mgr.save();
321357
}
322-
nwg::simple_message("已保存", "API Key 已成功保存!");
358+
nwg::simple_message("已保存", "配置已成功保存!");
323359
self.config_window.set_visible(false);
324360
}
325361

362+
fn reset_ai_config(&self) {
363+
#[cfg(target_os = "windows")]
364+
unsafe {
365+
use windows::Win32::UI::WindowsAndMessaging::{MessageBoxW, MB_YESNO, MB_ICONWARNING, IDYES};
366+
use windows::core::PCWSTR;
367+
let title = "确认重置\0".encode_utf16().collect::<Vec<u16>>();
368+
let msg = "将重置 URL 与 模型名称 为默认值\nApiKey将被清空\n确定继续?\0".encode_utf16().collect::<Vec<u16>>();
369+
let ret = MessageBoxW(None, PCWSTR(msg.as_ptr()), PCWSTR(title.as_ptr()), MB_YESNO | MB_ICONWARNING);
370+
if ret != IDYES {
371+
return;
372+
}
373+
}
374+
if let Some(mgr) = &*self.config_manager.borrow() {
375+
mgr.reset_ai_config();
376+
self.api_input.set_text(&mgr.get_api_key());
377+
self.url_input.set_text(&mgr.get_api_url());
378+
self.model_input.set_text(&mgr.get_model_name());
379+
let _ = mgr.save();
380+
}
381+
nwg::simple_message("已重置", "已重置 API Key、API URL 与模型为默认值");
382+
}
383+
326384
fn check_update(&self) {
327385
let current_version = CURRENT_VERSION.to_string();
328386
std::thread::spawn(move || {

src/main.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -265,12 +265,12 @@ async fn process_audio_and_type(buffer: Arc<Mutex<Vec<f32>>>, sample_rate: u32,
265265

266266
let client = reqwest::Client::new();
267267
let form = reqwest::multipart::Form::new()
268-
.text("model", "FunAudioLLM/SenseVoiceSmall")
268+
.text("model", config.get_model_name())
269269
.part("file", reqwest::multipart::Part::bytes(wav_data)
270270
.file_name("recording.wav")
271271
.mime_str("audio/wav")?);
272272

273-
let resp = client.post("https://api.siliconflow.cn/v1/audio/transcriptions")
273+
let resp = client.post(&config.get_api_url())
274274
.header("Authorization", format!("Bearer {}", api_key))
275275
.multipart(form)
276276
.send()

0 commit comments

Comments
 (0)