-
void load()}
- disabled={status === 'loading'}
- >
- {status === 'loading' ? t('history.audioLoading') : t('history.playRecording')}
-
- {status === 'error' && (
-
{errorText}
- )}
+ return status === 'loading' || status === 'error' ? (
+
+ {status === 'loading' ? t('history.audioLoading') : errorText}
- );
+ ) : null;
}
/** 流水线单步耗时:<1s 显示整数毫秒(流式收尾常在几十 ms,0.1s 精度会把不同结果
diff --git a/openless-all/app/src/pages/QuickNote.tsx b/openless-all/app/src/pages/QuickNote.tsx
new file mode 100644
index 000000000..87294a366
--- /dev/null
+++ b/openless-all/app/src/pages/QuickNote.tsx
@@ -0,0 +1,100 @@
+import { useState } from 'react';
+import { useTranslation } from 'react-i18next';
+import { ShortcutRecorder } from '../components/ShortcutRecorder';
+import { isDesktop, setQuickNoteHotkey } from '../lib/ipc';
+import { useHotkeySettings } from '../state/HotkeySettingsContext';
+import { Btn, Card } from './_atoms';
+import { History } from './History';
+
+const QUICK_NOTE_SHORTCUT_HIDDEN_KEY = 'openless.quick-note.shortcut-hidden';
+
+/** Quick notes share the unified history/actions surface but use permanent audio retention. */
+export function QuickNote() {
+ const { t } = useTranslation();
+ const { prefs, updatePrefs } = useHotkeySettings();
+ const [shortcutVisible, setShortcutVisible] = useState(() => {
+ try {
+ return window.localStorage.getItem(QUICK_NOTE_SHORTCUT_HIDDEN_KEY) !== '1';
+ } catch {
+ return true;
+ }
+ });
+
+ const hideShortcut = () => {
+ setShortcutVisible(false);
+ try {
+ window.localStorage.setItem(QUICK_NOTE_SHORTCUT_HIDDEN_KEY, '1');
+ } catch {
+ // The layout preference is best-effort when localStorage is unavailable.
+ }
+ };
+
+ const showShortcut = () => {
+ setShortcutVisible(true);
+ try {
+ window.localStorage.removeItem(QUICK_NOTE_SHORTCUT_HIDDEN_KEY);
+ } catch {
+ // The layout preference is best-effort when localStorage is unavailable.
+ }
+ };
+
+ return (
+
+ {isDesktop() && shortcutVisible && (
+
+
+
+ {t('quickNote.shortcutTitle', 'Quick note shortcut')}
+
+
+
+
+ {t(
+ 'quickNote.shortcutDesc',
+ 'Press once to start a permanent audio capture, then press again to finish.',
+ )}
+
+ {prefs && (
+ {
+ await setQuickNoteHotkey(binding);
+ await updatePrefs({ ...prefs, quickNoteHotkey: binding });
+ }}
+ onDisable={async () => {
+ await setQuickNoteHotkey(null);
+ await updatePrefs({ ...prefs, quickNoteHotkey: null });
+ }}
+ />
+ )}
+
+ )}
+ {isDesktop() && !shortcutVisible && (
+
+
+ {t('quickNote.showShortcut', '显示速记快捷键')}
+
+
+ )}
+
+
+
+
+ );
+}
diff --git a/openless-all/app/src/pages/_atoms.tsx b/openless-all/app/src/pages/_atoms.tsx
index fc2487a52..4d693cb18 100644
--- a/openless-all/app/src/pages/_atoms.tsx
+++ b/openless-all/app/src/pages/_atoms.tsx
@@ -189,6 +189,9 @@ interface BtnProps {
variant?: BtnVariant;
size?: BtnSize;
icon?: string;
+ ariaLabel?: string;
+ ariaExpanded?: boolean;
+ title?: string;
style?: CSSProperties;
onClick?: () => void;
disabled?: boolean;
@@ -199,6 +202,9 @@ export function Btn({
variant = 'ghost',
size = 'md',
icon,
+ ariaLabel,
+ ariaExpanded,
+ title,
style,
onClick,
disabled = false,
@@ -233,6 +239,9 @@ export function Btn({
+
+ {
+ await setQuickNoteHotkey(binding);
+ await savePrefs({ ...prefs, quickNoteHotkey: binding });
+ }}
+ onDisable={async () => {
+ await setQuickNoteHotkey(null);
+ await savePrefs({ ...prefs, quickNoteHotkey: null });
+ }}
+ />
+