From 4798647b40249bb3ad99ada36355ead4884dad0b Mon Sep 17 00:00:00 2001 From: kura Date: Thu, 19 Mar 2026 15:37:19 +0800 Subject: [PATCH] =?UTF-8?q?=E7=BF=BB=E8=AF=91=E8=B0=83=E6=95=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src-tauri/src/task.rs | 52 ++++++++++++++---- src-tauri/src/translate.rs | 22 +++++++- .../licenses_bundle/homebrew-licenses.zip | Bin 489008 -> 489008 bytes src/App.vue | 3 + src/components/SubtitleEditor.vue | 8 ++- src/stores/tasks.ts | 10 ++++ src/style.css | 16 ++++++ 7 files changed, 98 insertions(+), 13 deletions(-) diff --git a/src-tauri/src/task.rs b/src-tauri/src/task.rs index 1c37092..1ae9a20 100644 --- a/src-tauri/src/task.rs +++ b/src-tauri/src/task.rs @@ -208,15 +208,7 @@ async fn run_pipeline( }, |segment| { if let Ok(mut current_task) = app_state_for_segment.get_task(&task_id_for_segment) { - if let Some(existing) = current_task - .segments - .iter_mut() - .find(|item| item.id == segment.id) - { - *existing = segment.clone(); - } else { - current_task.segments.push(segment.clone()); - } + upsert_segment(&mut current_task.segments, segment.clone()); let _ = app_state_for_segment.upsert_task(current_task); } window.emit( @@ -242,7 +234,31 @@ async fn run_pipeline( .ok_or_else(|| anyhow::anyhow!("翻译模式需要填写 LLM API 配置,或设置 OPENAI_API_BASE / OPENAI_API_KEY"))?; set_status(&window, &app_state, &mut task, TaskStatus::Translating, 72.0, "正在生成译文")?; let translator = Translator::new(config)?; - segments = translator.translate_segments(&segments, &task.target_lang).await?; + let task_id_for_translate = task.id.clone(); + let app_state_for_translate = app_state.clone(); + let window_for_translate = window.clone(); + segments = translator + .translate_segments_with_progress( + &segments, + &task.target_lang, + |message| { + let _ = emit_log(&window_for_translate, &task_id_for_translate, message); + }, + |segment| { + if let Ok(mut current_task) = app_state_for_translate.get_task(&task_id_for_translate) { + upsert_segment(&mut current_task.segments, segment.clone()); + let _ = app_state_for_translate.upsert_task(current_task); + } + let _ = window_for_translate.emit( + "task:segment", + crate::models::SegmentEvent { + task_id: task_id_for_translate.clone(), + segment, + }, + ); + }, + ) + .await?; task.segments = segments.clone(); app_state.upsert_task(task.clone())?; @@ -352,3 +368,19 @@ fn emit_log(window: &Window, task_id: &str, message: String) -> Result<()> { )?; Ok(()) } + +fn upsert_segment(segments: &mut Vec, segment: SubtitleSegment) { + if let Some(existing) = segments.iter_mut().find(|item| item.id == segment.id) { + *existing = segment; + } else { + segments.push(segment); + } + + segments.sort_by(|left, right| { + left.start + .partial_cmp(&right.start) + .unwrap_or(std::cmp::Ordering::Equal) + .then_with(|| left.end.partial_cmp(&right.end).unwrap_or(std::cmp::Ordering::Equal)) + .then_with(|| left.id.cmp(&right.id)) + }); +} diff --git a/src-tauri/src/translate.rs b/src-tauri/src/translate.rs index d0b4d9e..ad218b9 100644 --- a/src-tauri/src/translate.rs +++ b/src-tauri/src/translate.rs @@ -70,11 +70,17 @@ impl Translator { Ok(Self { client, config }) } - pub async fn translate_segments( + pub async fn translate_segments_with_progress( &self, segments: &[SubtitleSegment], target_language: &TargetLanguage, - ) -> Result> { + mut log: LF, + mut emit_segment: SF, + ) -> Result> + where + LF: FnMut(String), + SF: FnMut(SubtitleSegment), + { let batch_size = self.config.batch_size.clamp(10, 15); let context_size = self.config.context_size.min(5); let mut translated = segments.to_vec(); @@ -88,13 +94,25 @@ impl Translator { let context_start = batch_start.saturating_sub(context_size); let context = &segments[context_start..batch_start]; let batch = &segments[batch_start..batch_end]; + log(format!( + "translation: batch {}-{}, segments={}", + batch_start + 1, + batch_end, + batch + .iter() + .map(|segment| segment.id.as_str()) + .collect::>() + .join(", ") + )); let rows = self .translate_batch_with_retries(context, batch, target_language_name) .await?; + log(format!("translation: batch done, translated={}", rows.len())); for row in rows { if let Some(segment) = translated.iter_mut().find(|item| item.id == row.id) { segment.translated_text = Some(row.text); + emit_segment(segment.clone()); } } } diff --git a/src-tauri/vendor/licenses_bundle/homebrew-licenses.zip b/src-tauri/vendor/licenses_bundle/homebrew-licenses.zip index 13dd891051b46324cd5223480401082f355ab616..2b3b8c0a1a8390e6ac765bd0921208fd778f2e22 100644 GIT binary patch delta 2122 zcmY*ZU2Icj7`Eq}_Uqs3a4Sx1xA7xGP+?fd>EF6`?YgxD36LhnB~ywUOAOmGqsS7X zDRIihqB#o{ydcDog`bN-AWcljE{q9?ECh&m7`@=agkVOZOuf+WecrRfDGA^Ez2|$M zpZEQ~ZvJ6-{$bd^yQpf-&(Ob9Q(yVp6}i5)f!2NNY5n*WTIbf$dK2p(v>HtgIyQ92 zS_*GFIw%}-yhPywpWSv4kMS3WkODwj0pNf_O2%jW@q)6Evbzf4|0uwi@)9!ox#1$@#r>|WV69xm^%FWuu_@nFojVfC~d zL%#0bwBgj$+fmv^8r`eUAd{g85f~ylP;PmxfGL0 zP8Thdd`c%%x_F+FSzRP3uIiQ_AL(MXm8^@}l9HJKdpye6-wG155EVhf45ei9I4WMG zWOrI7TVmn`N`ToPV+@>BW9&pG5vxLt+gSBDv&t^EnH8^7Jd=^ccj96VB~bAuSOvuC z1QTB){f`nNL}h60Qi8jI%?Am#2~2B}G3aJrl69doon#$g7LtrXH_J)(Dyg?yw%+Zw zm7nakP2x_s&7&v9dU}uam`aIOB9Ibvk36d@DQ1V%?`FbYdn|0sgoUbK_OKK@-{@fz zD0if}97H?P%qpiw?}y*Sw`PSLm`)!j`lP9*j0juoyE3*}O=p-;N_9~Mi2uyk>~8M0 zB}4XP)=Y3~H+r8=5FBIi6Ez-uB~o&rBeGGSA|d^CC$^ zOL-Be2w0(?VURr9&ywi-YCrc4-ADbb3&OPnObCL~02AQkxOsx{EDYFw>PrLGJ%y?r z1>0XrB|@4QmrN;cEHP}$KoAn5 zDRIKXg1HNo_&|su3&O)7nkFV>55@#U7Xrq&8GYcvgg{23OnuPv{l2@)EeYp4-#y>& z&-b0v%{>UuJqY{v6jiPH8Tz+)#{fR1a{d5zC+YmgWhK|?s7B}9vJGX>&!%V}~1 z8^=aQw%R8niG#+hGeD&T{*l3pPrd|s~6lD z@=f=~^@~R)2k|P*MU(OUMoho5*MAHe@B~N4!GLx$xZc*xCm97bkR)7wU|tD zx@e)~V>&sZi{~ks(M5vds&4u5p)OWi$-1a5DVYhdXQGV#y&y63Q4u7}P)a6`qvAzM z_M~O9B_>{=1epCX#=uE6#!h4su`1NKja82`tL$Q%S+RrS#f&7r8y9ORfr>Z5Dj-fL znD_?iA5Dl5m7%q33GN0q?z#T5ba7ctDG9WAAS>GniX{Ab2ycXO{T z8L}rcW`bM0)%$cJ=dw(SF?VHI4mUWKI|>pW*@%>l>cj%<2iNaZ9k6p%mm`+^DKTnFOo#G zloxS|fED@~2Fa8CEQ!9q>F2(o`>>yNLAZ8+2|-XAU;><+F;6g_`2pKceQChDr%<({ zV4KZQf!WZ~Oo7WF`a^+5WzuO)U|Z4ldngx~2<2axC)bN&E0x31e??|TMP#={7^;qN kQaia@ CrossSubtitle 桌面字幕工作台 + + 作者:kuraa gpt5.4 +