fix tray style activation and streamed text consistency
This commit is contained in:
1 parent
16dd99cf67
commit
a4c4cc224b
5 files changed
+179
-75
No files matched your search
@@ -200,7 +200,7 @@ fn emit_prefs_changed(app: &AppHandle, prefs: &UserPreferences) {
|
||||
let _ = app.emit_to("main", "prefs:changed", prefs);
|
||||
}
|
||||
|
||||
fn sync_style_pack_prefs_and_persist(
|
||||
pub(crate) fn sync_style_pack_prefs_and_persist(
|
||||
coord: &Coordinator,
|
||||
app: &AppHandle,
|
||||
mut prefs: UserPreferences,
|
||||
@@ -216,6 +216,54 @@ fn sync_style_pack_prefs_and_persist(
|
||||
Ok(prefs)
|
||||
}
|
||||
|
||||
pub(crate) fn activate_style_pack_by_id(
|
||||
coord: &Coordinator,
|
||||
app: &AppHandle,
|
||||
id: &str,
|
||||
) -> Result<StylePack, String> {
|
||||
let mut prefs = coord.prefs().get();
|
||||
let pack = coord.style_packs().get(id).map_err(|e| e.to_string())?;
|
||||
log::info!(
|
||||
"[style-pack] activate helper requested id={} kind={:?} base_mode={:?} enabled={}",
|
||||
pack.id,
|
||||
pack.kind,
|
||||
pack.base_mode,
|
||||
pack.enabled
|
||||
);
|
||||
if !pack.enabled {
|
||||
coord
|
||||
.style_packs()
|
||||
.set_enabled(id, true)
|
||||
.map_err(|e| e.to_string())?;
|
||||
}
|
||||
prefs.active_style_pack_id = id.to_string();
|
||||
sync_style_pack_prefs_and_persist(coord, app, prefs)?;
|
||||
log::info!("[style-pack] activate helper applied id={id}");
|
||||
coord
|
||||
.style_packs()
|
||||
.get(id)
|
||||
.map(|mut pack| {
|
||||
pack.active = true;
|
||||
pack
|
||||
})
|
||||
.map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
pub(crate) fn activate_builtin_style_mode(
|
||||
coord: &Coordinator,
|
||||
app: &AppHandle,
|
||||
mode: PolishMode,
|
||||
) -> Result<(), String> {
|
||||
let pack_id = builtin_style_pack_id(mode).to_string();
|
||||
log::info!(
|
||||
"[style-pack] activate builtin mode helper mode={:?} pack_id={}",
|
||||
mode,
|
||||
pack_id
|
||||
);
|
||||
let _ = activate_style_pack_by_id(coord, app, &pack_id)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// ─────────────────────────── release channel (Beta opt-in) ───────────────────────────
|
||||
//
|
||||
// 渠道偏好的写入路径跟 set_settings 复用 persist_settings:保持热键兜底归一化
|
||||
@@ -1243,32 +1291,7 @@ pub fn set_active_style_pack(
|
||||
app: AppHandle,
|
||||
id: String,
|
||||
) -> Result<StylePack, String> {
|
||||
let mut prefs = coord.prefs().get();
|
||||
let pack = coord.style_packs().get(&id).map_err(|e| e.to_string())?;
|
||||
log::info!(
|
||||
"[style-pack] command activate requested id={} kind={:?} base_mode={:?} enabled={}",
|
||||
pack.id,
|
||||
pack.kind,
|
||||
pack.base_mode,
|
||||
pack.enabled
|
||||
);
|
||||
if !pack.enabled {
|
||||
coord
|
||||
.style_packs()
|
||||
.set_enabled(&id, true)
|
||||
.map_err(|e| e.to_string())?;
|
||||
}
|
||||
prefs.active_style_pack_id = id.clone();
|
||||
sync_style_pack_prefs_and_persist(&*coord, &app, prefs)?;
|
||||
log::info!("[style-pack] command activate applied id={id}");
|
||||
coord
|
||||
.style_packs()
|
||||
.get(&id)
|
||||
.map(|mut pack| {
|
||||
pack.active = true;
|
||||
pack
|
||||
})
|
||||
.map_err(|e| e.to_string())
|
||||
activate_style_pack_by_id(&coord, &app, &id)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
@@ -1369,20 +1392,7 @@ pub fn set_default_polish_mode(
|
||||
app: AppHandle,
|
||||
mode: PolishMode,
|
||||
) -> Result<(), String> {
|
||||
let mut prefs = coord.prefs().get();
|
||||
let pack_id = builtin_style_pack_id(mode).to_string();
|
||||
log::info!(
|
||||
"[style-pack] compat set_default_polish_mode mode={:?} pack_id={}",
|
||||
mode,
|
||||
pack_id
|
||||
);
|
||||
coord
|
||||
.style_packs()
|
||||
.set_enabled(&pack_id, true)
|
||||
.map_err(|e| e.to_string())?;
|
||||
prefs.active_style_pack_id = pack_id;
|
||||
let _ = sync_style_pack_prefs_and_persist(&*coord, &app, prefs)?;
|
||||
Ok(())
|
||||
activate_builtin_style_mode(&coord, &app, mode)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
|
||||
@@ -273,6 +273,44 @@ async fn run_streaming_polish(
|
||||
}
|
||||
}
|
||||
|
||||
fn finalize_polished_text(
|
||||
polished: String,
|
||||
translation_active: bool,
|
||||
raw_uses_llm: bool,
|
||||
mode: PolishMode,
|
||||
polish_error: &Option<String>,
|
||||
chinese_script_preference: crate::types::ChineseScriptPreference,
|
||||
correction_rules: &[crate::types::CorrectionRule],
|
||||
already_streamed: bool,
|
||||
) -> String {
|
||||
if already_streamed {
|
||||
return polished;
|
||||
}
|
||||
let should_force_script = if translation_active {
|
||||
polish_error.is_some()
|
||||
} else {
|
||||
(!raw_uses_llm && mode == PolishMode::Raw) || polish_error.is_some()
|
||||
};
|
||||
let polished = if should_force_script {
|
||||
apply_chinese_script_preference(&polished, chinese_script_preference)
|
||||
} else {
|
||||
polished
|
||||
};
|
||||
if correction_rules.is_empty() {
|
||||
polished
|
||||
} else {
|
||||
let corrected = apply_correction_rules(&polished, correction_rules);
|
||||
if corrected != polished {
|
||||
log::info!(
|
||||
"[coord] correction rules adjusted final text ({} → {} chars)",
|
||||
polished.chars().count(),
|
||||
corrected.chars().count()
|
||||
);
|
||||
}
|
||||
corrected
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) async fn handle_pressed_edge(inner: &Arc<Inner>) {
|
||||
let was_held = inner.hotkey_trigger_held.swap(true, Ordering::SeqCst);
|
||||
if !was_held {
|
||||
@@ -1334,32 +1372,16 @@ pub(super) async fn end_session(inner: &Arc<Inner>) -> Result<(), String> {
|
||||
(p, e, false)
|
||||
};
|
||||
|
||||
// 仅在“ASR 直出文本”场景做强制简繁收敛,避免误伤成功的翻译/常规 LLM 输出:
|
||||
// - 非翻译模式:mode=Raw(本来就不走润色)或润色失败回退 raw
|
||||
// - 翻译模式:仅翻译失败回退 raw 时才收敛
|
||||
let should_force_script = if translation_active {
|
||||
polish_error.is_some()
|
||||
} else {
|
||||
(!raw_uses_llm && mode == PolishMode::Raw) || polish_error.is_some()
|
||||
};
|
||||
let polished = if should_force_script {
|
||||
apply_chinese_script_preference(&polished, chinese_script_preference)
|
||||
} else {
|
||||
polished
|
||||
};
|
||||
let polished = if correction_rules.is_empty() {
|
||||
polished
|
||||
} else {
|
||||
let corrected = apply_correction_rules(&polished, &correction_rules);
|
||||
if corrected != polished {
|
||||
log::info!(
|
||||
"[coord] correction rules adjusted final text ({} → {} chars)",
|
||||
polished.chars().count(),
|
||||
corrected.chars().count()
|
||||
);
|
||||
}
|
||||
corrected
|
||||
};
|
||||
let polished = finalize_polished_text(
|
||||
polished,
|
||||
translation_active,
|
||||
raw_uses_llm,
|
||||
mode,
|
||||
&polish_error,
|
||||
chinese_script_preference,
|
||||
&correction_rules,
|
||||
already_streamed,
|
||||
);
|
||||
// 原子化最后一次 cancel 检查 + 转 Inserting:
|
||||
// 在同一 lock 内决定「丢弃」还是「进入 Inserting」。一旦设到 Inserting,
|
||||
// cancel_session 就拒绝介入(Cmd+V 已发出,撤销不掉)。这是 audit HIGH #2 的修复,
|
||||
@@ -1570,3 +1592,55 @@ pub(super) fn cancel_session(inner: &Arc<Inner>) {
|
||||
log::info!("[coord] session cancelled (was {:?})", decision.phase);
|
||||
schedule_capsule_idle(inner, CAPSULE_AUTO_HIDE_DELAY_MS);
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::finalize_polished_text;
|
||||
use crate::types::{ChineseScriptPreference, CorrectionRule, PolishMode};
|
||||
|
||||
fn correction_rule(pattern: &str, replacement: &str) -> CorrectionRule {
|
||||
CorrectionRule {
|
||||
id: "test".into(),
|
||||
pattern: pattern.into(),
|
||||
replacement: replacement.into(),
|
||||
enabled: true,
|
||||
created_at: String::new(),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn streamed_output_skips_postprocessing_mutations() {
|
||||
let rules = vec![correction_rule("Open AI", "OpenAI")];
|
||||
|
||||
let result = finalize_polished_text(
|
||||
"Open AI".into(),
|
||||
false,
|
||||
false,
|
||||
PolishMode::Raw,
|
||||
&None,
|
||||
ChineseScriptPreference::Auto,
|
||||
&rules,
|
||||
true,
|
||||
);
|
||||
|
||||
assert_eq!(result, "Open AI");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn non_streamed_output_still_applies_correction_rules() {
|
||||
let rules = vec![correction_rule("Open AI", "OpenAI")];
|
||||
|
||||
let result = finalize_polished_text(
|
||||
"Open AI".into(),
|
||||
false,
|
||||
false,
|
||||
PolishMode::Raw,
|
||||
&None,
|
||||
ChineseScriptPreference::Auto,
|
||||
&rules,
|
||||
false,
|
||||
);
|
||||
|
||||
assert_eq!(result, "OpenAI");
|
||||
}
|
||||
}
|
||||
@@ -476,7 +476,12 @@ fn build_style_tray_menu<M: Manager<tauri::Wry>>(
|
||||
app: &M,
|
||||
coordinator: &Arc<coordinator::Coordinator>,
|
||||
) -> tauri::Result<StyleTrayMenu> {
|
||||
let selected = coordinator.prefs().get().default_mode;
|
||||
let prefs = coordinator.prefs().get();
|
||||
let selected = coordinator
|
||||
.style_packs()
|
||||
.get_or_default_active(&prefs.active_style_pack_id)
|
||||
.map(|pack| pack.base_mode)
|
||||
.unwrap_or(prefs.default_mode);
|
||||
let mut submenu = SubmenuBuilder::with_id(app, "style", "输出风格");
|
||||
for entry in tray_polish_mode_menu_entries(selected) {
|
||||
let item = CheckMenuItemBuilder::with_id(&entry.id, entry.label)
|
||||
@@ -630,14 +635,10 @@ fn handle_style_tray_menu_event(app: &AppHandle, id: &str) -> bool {
|
||||
return false;
|
||||
};
|
||||
let coord = app.state::<Arc<coordinator::Coordinator>>();
|
||||
let mut prefs = coord.prefs().get();
|
||||
prefs.default_mode = mode;
|
||||
if let Err(err) = coord.prefs().set(prefs.clone()) {
|
||||
log::warn!("[tray] save polish mode preference failed: {err}");
|
||||
if let Err(err) = commands::activate_builtin_style_mode(&coord, app, mode) {
|
||||
log::warn!("[tray] activate builtin style mode failed: {err}");
|
||||
return true;
|
||||
}
|
||||
let _ = app.emit("prefs:changed", &prefs);
|
||||
let _ = app.emit_to("main", "prefs:changed", &prefs);
|
||||
if let Err(err) = refresh_tray_microphone_menu(app) {
|
||||
log::warn!("[tray] refresh style menu after polish mode change failed: {err}");
|
||||
}
|
||||
|
||||
@@ -1012,7 +1012,7 @@ impl StylePackStore {
|
||||
}
|
||||
if let Some(pack) = packs
|
||||
.iter()
|
||||
.find(|pack| pack.id == BUILTIN_STYLE_PACK_LIGHT_ID)
|
||||
.find(|pack| pack.id == BUILTIN_STYLE_PACK_LIGHT_ID && pack.enabled)
|
||||
.cloned()
|
||||
{
|
||||
return Ok(pack);
|
||||
|
||||
@@ -195,6 +195,7 @@ impl StyleSystemPrompts {
|
||||
}
|
||||
|
||||
pub fn with_legacy_custom_prompts(mut self, legacy: &CustomStylePrompts) -> Self {
|
||||
const LEGACY_CUSTOM_PROMPT_MARKER: &str = "\n\n# 用户自定义附加要求\n";
|
||||
for mode in [
|
||||
PolishMode::Raw,
|
||||
PolishMode::Light,
|
||||
@@ -205,6 +206,9 @@ impl StyleSystemPrompts {
|
||||
if legacy_prompt.is_empty() {
|
||||
continue;
|
||||
}
|
||||
if self.for_mode(mode).contains(LEGACY_CUSTOM_PROMPT_MARKER) {
|
||||
continue;
|
||||
}
|
||||
let merged = format!(
|
||||
"{}\n\n# 用户自定义附加要求\n{}",
|
||||
self.for_mode(mode).trim_end(),
|
||||
@@ -1594,6 +1598,21 @@ mod tests {
|
||||
assert!(prefs.custom_style_prompts.has_for_mode(PolishMode::Formal));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn legacy_custom_style_prompts_are_not_appended_twice() {
|
||||
let base = StyleSystemPrompts::default();
|
||||
let legacy = CustomStylePrompts {
|
||||
light: "更像微信消息".into(),
|
||||
..CustomStylePrompts::default()
|
||||
};
|
||||
|
||||
let once = base.clone().with_legacy_custom_prompts(&legacy);
|
||||
let twice = once.clone().with_legacy_custom_prompts(&legacy);
|
||||
|
||||
assert_eq!(once.light, twice.light);
|
||||
assert_eq!(twice.light.matches("# 用户自定义附加要求").count(), 1);
|
||||
}
|
||||
|
||||
/// issue #360: 默认值必须是 CtrlV,跟历史行为一致;老配置文件没有
|
||||
/// pasteShortcut 字段时反序列化也得回到 CtrlV,否则会把现有用户的粘贴
|
||||
/// 行为静默改掉。
|
||||
|
||||
Reference in new issue
Block a user