From 13c34744cca988c34ff8a26821f92b5c9c3e62f1 Mon Sep 17 00:00:00 2001 From: Michealmyson Date: Mon, 11 May 2026 22:19:47 +0800 Subject: [PATCH] Improve capsule recording feedback --- .../src-tauri/src/coordinator/dictation.rs | 12 +++-- openless-all/app/src-tauri/src/types.rs | 1 + openless-all/app/src/components/Capsule.tsx | 50 +++++++++++++++++-- openless-all/app/src/i18n/en.ts | 6 ++- openless-all/app/src/i18n/ja.ts | 4 ++ openless-all/app/src/i18n/ko.ts | 4 ++ openless-all/app/src/i18n/zh-CN.ts | 6 ++- openless-all/app/src/i18n/zh-TW.ts | 6 ++- openless-all/app/src/lib/types.ts | 1 + 9 files changed, 79 insertions(+), 11 deletions(-) diff --git a/openless-all/app/src-tauri/src/coordinator/dictation.rs b/openless-all/app/src-tauri/src/coordinator/dictation.rs index 0a92b475..7e441378 100644 --- a/openless-all/app/src-tauri/src/coordinator/dictation.rs +++ b/openless-all/app/src-tauri/src/coordinator/dictation.rs @@ -1048,6 +1048,8 @@ pub(super) async fn end_session(inner: &Arc) -> Result<(), String> { return Ok(()); } + emit_capsule(inner, CapsuleState::Inserting, 0.0, elapsed, None, None); + let focus_target = inner.state.lock().focus_target; let focus_ready_for_paste = restore_focus_target_if_possible(focus_target); let prefs = inner.prefs.get(); @@ -1142,15 +1144,15 @@ pub(super) async fn end_session(inner: &Arc) -> Result<(), String> { Some("TSF 未上屏,已禁止非 TSF 兜底".to_string()) } else if polish_error.is_some() { // polish 失败优先告知用户,即使 insert 成功也要让用户知道这版是原文 - Some("润色失败,已插入原文".to_string()) + Some(format!("润色失败,已插入识别文本 {inserted_chars} 字")) } else { match status { - InsertStatus::Inserted => None, - InsertStatus::PasteSent => Some("已尝试粘贴".to_string()), + InsertStatus::Inserted => Some(format!("已润色并插入 {inserted_chars} 字")), + InsertStatus::PasteSent => Some(format!("已润色,尝试插入 {inserted_chars} 字")), InsertStatus::CopiedFallback => Some(if cfg!(target_os = "windows") { - "已复制,请 Ctrl+V".to_string() + format!("已润色并复制 {inserted_chars} 字,请 Ctrl+V") } else { - "已复制,请粘贴".to_string() + format!("已润色并复制 {inserted_chars} 字,请粘贴") }), InsertStatus::Failed => Some("插入失败".to_string()), } diff --git a/openless-all/app/src-tauri/src/types.rs b/openless-all/app/src-tauri/src/types.rs index 3aac1cb5..fb80cbf1 100644 --- a/openless-all/app/src-tauri/src/types.rs +++ b/openless-all/app/src-tauri/src/types.rs @@ -1104,6 +1104,7 @@ pub enum CapsuleState { Recording, Transcribing, Polishing, + Inserting, Done, Cancelled, Error, diff --git a/openless-all/app/src/components/Capsule.tsx b/openless-all/app/src/components/Capsule.tsx index 848b9a9b..4792e356 100644 --- a/openless-all/app/src/components/Capsule.tsx +++ b/openless-all/app/src/components/Capsule.tsx @@ -76,6 +76,13 @@ function ProcessingDots() { ); } +function formatElapsed(ms: number) { + const totalSeconds = Math.max(0, Math.floor(ms / 1000)); + const minutes = Math.floor(totalSeconds / 60); + const seconds = totalSeconds % 60; + return `${String(minutes).padStart(2, '0')}:${String(seconds).padStart(2, '0')}`; +} + interface CenterTextProps { os: OS; kind: 'default' | 'processing' | 'error'; @@ -164,13 +171,14 @@ interface PillProps { os: OS; state: CapsuleState; level: number; + elapsedMs: number; insertedChars: number; message?: string; onCancel: () => void; onConfirm: () => void; } -function Pill({ os, state, level, insertedChars, message, onCancel, onConfirm }: PillProps) { +function Pill({ os, state, level, elapsedMs, insertedChars, message, onCancel, onConfirm }: PillProps) { const { t } = useTranslation(); const metrics = getCapsulePillMetrics(os); const processingLayout = getCapsuleMessageLayout(os, 'processing'); @@ -179,10 +187,42 @@ function Pill({ os, state, level, insertedChars, message, onCancel, onConfirm }: let center: JSX.Element; switch (state) { case 'recording': - center = ; + center = ( +
+ + + {t('capsule.recordingElapsed', { time: formatElapsed(elapsedMs) })} + +
+ ); break; case 'transcribing': case 'polishing': + case 'inserting': { + const processingText = + state === 'transcribing' + ? t('capsule.transcribing') + : state === 'polishing' + ? t('capsule.polishing') + : t('capsule.inserting'); center = (
- {t('capsule.thinking')} + {processingText}
); break; + } case 'done': center = ; break; @@ -278,6 +319,7 @@ export function Capsule() { const metrics = getCapsulePillMetrics(os); const [state, setState] = useState(isTauri ? 'idle' : 'recording'); const [level, setLevel] = useState(isTauri ? 0 : 0.6); + const [elapsedMs, setElapsedMs] = useState(0); const [insertedChars, setInsertedChars] = useState(0); const [message, setMessage] = useState(); const [translation, setTranslation] = useState(false); @@ -294,6 +336,7 @@ export function Capsule() { const p = event.payload; setState(p.state); setLevel(p.level ?? 0); + setElapsedMs(p.elapsedMs ?? 0); setMessage(p.message ?? undefined); if (p.insertedChars != null) setInsertedChars(p.insertedChars); setTranslation(p.translation === true); @@ -390,6 +433,7 @@ export function Capsule() { os={os} state={state} level={level} + elapsedMs={elapsedMs} insertedChars={insertedChars} message={message} onCancel={onCancel} diff --git a/openless-all/app/src/i18n/en.ts b/openless-all/app/src/i18n/en.ts index a58e295f..7523067c 100644 --- a/openless-all/app/src/i18n/en.ts +++ b/openless-all/app/src/i18n/en.ts @@ -31,9 +31,13 @@ export const en: typeof zhCN = { }, capsule: { thinking: 'Thinking…', + recordingElapsed: 'Recording {{time}}', + transcribing: 'Transcribing…', + polishing: 'Polishing…', + inserting: 'Inserting…', cancelled: 'Cancelled', error: 'Something went wrong', - inserted: 'Inserted {{count}}', + inserted: 'Inserted {{count}} chars', translating: 'Translating', }, qa: { diff --git a/openless-all/app/src/i18n/ja.ts b/openless-all/app/src/i18n/ja.ts index e5a6eca1..10404400 100644 --- a/openless-all/app/src/i18n/ja.ts +++ b/openless-all/app/src/i18n/ja.ts @@ -33,6 +33,10 @@ export const ja: typeof zhCN = { }, capsule: { thinking: '考えています', + recordingElapsed: '録音中 {{time}}', + transcribing: '文字起こし中…', + polishing: '整えています…', + inserting: '入力中…', cancelled: 'キャンセルしました', error: 'エラーが発生しました', inserted: '{{count}} 文字を入力しました', diff --git a/openless-all/app/src/i18n/ko.ts b/openless-all/app/src/i18n/ko.ts index 535513e1..51b74647 100644 --- a/openless-all/app/src/i18n/ko.ts +++ b/openless-all/app/src/i18n/ko.ts @@ -33,6 +33,10 @@ export const ko: typeof zhCN = { }, capsule: { thinking: '생각 중', + recordingElapsed: '녹음 중 {{time}}', + transcribing: '인식 중…', + polishing: '다듬는 중…', + inserting: '입력 중…', cancelled: '취소됨', error: '오류 발생', inserted: '{{count}}자 입력됨', diff --git a/openless-all/app/src/i18n/zh-CN.ts b/openless-all/app/src/i18n/zh-CN.ts index 6c0aa3ff..85d9d794 100644 --- a/openless-all/app/src/i18n/zh-CN.ts +++ b/openless-all/app/src/i18n/zh-CN.ts @@ -29,9 +29,13 @@ export const zhCN = { }, capsule: { thinking: '正在思考中', + recordingElapsed: '录音中 {{time}}', + transcribing: '正在识别…', + polishing: '正在润色…', + inserting: '正在插入…', cancelled: '已取消', error: '出错了', - inserted: '已插入 {{count}}', + inserted: '已插入 {{count}} 字', translating: '正在翻译', }, qa: { diff --git a/openless-all/app/src/i18n/zh-TW.ts b/openless-all/app/src/i18n/zh-TW.ts index a10ccab5..8eac957c 100644 --- a/openless-all/app/src/i18n/zh-TW.ts +++ b/openless-all/app/src/i18n/zh-TW.ts @@ -31,9 +31,13 @@ export const zhTW: typeof zhCN = { }, capsule: { thinking: '正在思考中', + recordingElapsed: '錄音中 {{time}}', + transcribing: '正在識別…', + polishing: '正在潤色…', + inserting: '正在插入…', cancelled: '已取消', error: '出錯了', - inserted: '已插入 {{count}}', + inserted: '已插入 {{count}} 字', translating: '正在翻譯', }, qa: { diff --git a/openless-all/app/src/lib/types.ts b/openless-all/app/src/lib/types.ts index b4e6e164..52dec379 100644 --- a/openless-all/app/src/lib/types.ts +++ b/openless-all/app/src/lib/types.ts @@ -263,6 +263,7 @@ export type CapsuleState = | 'recording' | 'transcribing' | 'polishing' + | 'inserting' | 'done' | 'cancelled' | 'error';