Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion src-tauri/src/actions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -524,7 +524,9 @@ impl ShortcutAction for TranscribeAction {
let overlay_started = Instant::now();
match settings.overlay_style {
OverlayStyle::Live if model_supports_streaming => utils::show_streaming_overlay(app),
OverlayStyle::Live | OverlayStyle::Minimal => show_recording_overlay(app),
OverlayStyle::Live | OverlayStyle::Minimal => {
show_recording_overlay(app)
}
OverlayStyle::None => {} // show_overlay_state no-ops on None anyway
}
// Everything above runs before capture can begin, so each span here is
Expand Down
1 change: 1 addition & 0 deletions src-tauri/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -662,6 +662,7 @@ pub fn run(cli_args: CliArgs) {
shortcut::change_selected_language_setting,
shortcut::change_overlay_position_setting,
shortcut::change_overlay_style_setting,
shortcut::change_monochrome_overlay_setting,
shortcut::change_debug_mode_setting,
shortcut::change_word_correction_threshold_setting,
shortcut::change_extra_recording_buffer_setting,
Expand Down
7 changes: 5 additions & 2 deletions src-tauri/src/settings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -119,8 +119,8 @@ pub enum OverlayPosition {
Bottom,
}

/// Which recording overlay to display. `Minimal` and `Live` share one base
/// (the pill); `Live` grows into the panel that shows live transcription text.
/// Which recording overlay to display. `Minimal` and `Live`
/// share one pill; `Live` grows into the live transcription panel.
/// `None` hides the overlay entirely. Decoupled from whether the model runs in
/// streaming mode (that is driven purely by model capability).
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, Type)]
Expand Down Expand Up @@ -514,6 +514,8 @@ pub struct AppSettings {
/// `overlay_position` (position `none` → style `None`).
#[serde(default = "default_overlay_style")]
pub overlay_style: OverlayStyle,
#[serde(default)]
pub monochrome_overlay: bool,
}

fn default_model() -> String {
Expand Down Expand Up @@ -970,6 +972,7 @@ pub fn get_default_settings() -> AppSettings {
vad_enabled: default_vad_enabled(),
vad_backend: VadBackend::default(),
overlay_style: default_overlay_style(),
monochrome_overlay: false,
}
}

Expand Down
11 changes: 11 additions & 0 deletions src-tauri/src/shortcut/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -685,6 +685,7 @@ pub fn change_overlay_style_setting(app: AppHandle, style: String) -> Result<(),
};
settings.overlay_style = parsed;
settings::write_settings(&app, settings);
let _ = app.emit_to("recording_overlay", "overlay-style-changed", parsed);

// Keep the cached overlay-enabled flag in sync so emit_levels stops (or
// resumes) emitting on the next audio callback.
Expand All @@ -696,6 +697,16 @@ pub fn change_overlay_style_setting(app: AppHandle, style: String) -> Result<(),
Ok(())
}

#[tauri::command]
#[specta::specta]
pub fn change_monochrome_overlay_setting(app: AppHandle, enabled: bool) -> Result<(), String> {
let mut settings = settings::get_settings(&app);
settings.monochrome_overlay = enabled;
settings::write_settings(&app, settings);
let _ = app.emit_to("recording_overlay", "overlay-monochrome-changed", enabled);
Ok(())
}

#[tauri::command]
#[specta::specta]
pub fn change_debug_mode_setting(app: AppHandle, enabled: bool) -> Result<(), String> {
Expand Down
14 changes: 11 additions & 3 deletions src/bindings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,14 @@ async changeOverlayStyleSetting(style: string) : Promise<Result<null, string>> {
else return { status: "error", error: e as any };
}
},
async changeMonochromeOverlaySetting(enabled: boolean) : Promise<Result<null, string>> {
try {
return { status: "ok", data: await TAURI_INVOKE("change_monochrome_overlay_setting", { enabled }) };
} catch (e) {
if(e instanceof Error) throw e;
else return { status: "error", error: e as any };
}
},
async changeDebugModeSetting(enabled: boolean) : Promise<Result<null, string>> {
try {
return { status: "ok", data: await TAURI_INVOKE("change_debug_mode_setting", { enabled }) };
Expand Down Expand Up @@ -1004,7 +1012,7 @@ vad_backend?: VadBackend;
* not gated on this — that follows model capability. Migrated from the old
* `overlay_position` (position `none` → style `None`).
*/
overlay_style?: OverlayStyle }
overlay_style?: OverlayStyle; monochrome_overlay?: boolean }
export type AudioDevice = { index: string; name: string; is_default: boolean }
export type AutoSubmitKey = "enter" | "ctrl_enter" | "cmd_enter"
export type AvailableAccelerators = { transcribe: string[]; ort: string[]; gpu_devices: GpuDeviceOption[] }
Expand Down Expand Up @@ -1067,8 +1075,8 @@ export type ModelUnloadTimeout = "never" | "immediately" | "min_2" | "min_5" | "
export type OrtAcceleratorSetting = "auto" | "cpu" | "cuda" | "directml" | "rocm"
export type OverlayPosition = "top" | "bottom"
/**
* Which recording overlay to display. `Minimal` and `Live` share one base
* (the pill); `Live` grows into the panel that shows live transcription text.
* Which recording overlay to display. `Minimal` and `Live`
* share one pill; `Live` grows into the live transcription panel.
* `None` hides the overlay entirely. Decoupled from whether the model runs in
* streaming mode (that is driven purely by model capability).
*/
Expand Down
13 changes: 13 additions & 0 deletions src/components/settings/ShowOverlay.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import React from "react";
import { useTranslation } from "react-i18next";
import { Dropdown } from "../ui/Dropdown";
import { SettingContainer } from "../ui/SettingContainer";
import { ToggleSwitch } from "../ui/ToggleSwitch";
import { useSettings } from "../../hooks/useSettings";
import type { OverlayPosition, OverlayStyle } from "@/bindings";

Expand Down Expand Up @@ -66,6 +67,18 @@ export const ShowOverlay: React.FC<ShowOverlayProps> = React.memo(
/>
</SettingContainer>

{selectedStyle !== "none" && (
<ToggleSwitch
checked={getSetting("monochrome_overlay") ?? false}
onChange={(enabled) => updateSetting("monochrome_overlay", enabled)}
isUpdating={isUpdating("monochrome_overlay")}
label={t("settings.advanced.overlay.monochrome.label")}
description={t("settings.advanced.overlay.monochrome.description")}
descriptionMode={descriptionMode}
grouped={grouped}
/>
)}

{selectedStyle !== "none" && (
<SettingContainer
title={t("settings.advanced.overlay.position.title")}
Expand Down
6 changes: 5 additions & 1 deletion src/i18n/locales/en/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -302,13 +302,17 @@
"overlay": {
"style": {
"title": "Overlay",
"description": "Choose the recording overlay: None hides it, Minimal shows a compact pill, Live shows transcription in real time as you speak (streaming-capable models only — look for the Streaming badge in the model picker). On Linux 'None' is recommended.",
"description": "Choose the recording overlay: None hides it, Minimal shows a compact pill, and Live shows transcription in real time as you speak (streaming-capable models only — look for the Streaming badge in the model picker). On Linux 'None' is recommended.",
"options": {
"none": "None",
"minimal": "Minimal",
"live": "Live"
}
},
"monochrome": {
"label": "Monochrome overlay",
"description": "Use gray and white accents in the recording overlay."
},
"position": {
"title": "Overlay Position",
"description": "Where the overlay appears on screen during recording and transcription.",
Expand Down
4 changes: 4 additions & 0 deletions src/overlay/RecordingOverlay.css
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,10 @@
align-items: flex-end; /* bottom placement: flush to the bottom edge */
font-family: var(--s-font);
}
.ov-stage.monochrome {
--s-accent: color-mix(in srgb, var(--color-text) 72%, var(--color-background));
--s-accent-soft: color-mix(in srgb, var(--color-text) 18%, transparent);
}
.ov-stage.top {
align-items: flex-start; /* top placement: flush to the top edge */
}
Expand Down
12 changes: 10 additions & 2 deletions src/overlay/RecordingOverlay.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ const RecordingOverlay: React.FC = () => {
// Overlay placement (top vs bottom of the screen). The Live panel grows downward
// from a top overlay (oldest line under the pill) and upward from a bottom one.
const [position, setPosition] = useState<"top" | "bottom">("bottom");
const [monochrome, setMonochrome] = useState(false);
// True once live text overflows the cap. A top overlay fades its top edge only
// while overflowing, so the resting first line stays crisp flush under the pill.
const [overflowing, setOverflowing] = useState(false);
Expand Down Expand Up @@ -75,6 +76,7 @@ const RecordingOverlay: React.FC = () => {
setPosition(
settings.data.overlay_position === "top" ? "top" : "bottom",
);
setMonochrome(settings.data.monochrome_overlay ?? false);
}
} catch {
// Keep the previous/default placement if settings can't be read.
Expand All @@ -94,6 +96,11 @@ const RecordingOverlay: React.FC = () => {
setCaptureReady(false);
});

const unlistenMonochrome = await listen<boolean>(
"overlay-monochrome-changed",
(event) => setMonochrome(event.payload),
);

const unlistenReady = await listen("recording-ready", () => {
setElapsed(0);
setCaptureReady(true);
Expand Down Expand Up @@ -124,6 +131,7 @@ const RecordingOverlay: React.FC = () => {
return () => {
unlistenShow();
unlistenHide();
unlistenMonochrome();
unlistenReady();
unlistenLevel();
unlistenStream();
Expand Down Expand Up @@ -240,7 +248,7 @@ const RecordingOverlay: React.FC = () => {
const collapsed = working && !hasText;

return (
<div dir={direction} className={`ov-stage ${position}`}>
<div dir={direction} className={`ov-stage ${position} ${monochrome ? "monochrome" : ""}`}>
<div
key={session}
className={`scard ${open ? "open" : ""} ${collapsed ? "working" : ""} ${
Expand Down Expand Up @@ -291,7 +299,7 @@ const RecordingOverlay: React.FC = () => {
return (
<div
dir={direction}
className={`ov-stage ${position} ov-fade ${isVisible ? "show" : ""}`}
className={`ov-stage ${position} ov-fade ${monochrome ? "monochrome" : ""} ${isVisible ? "show" : ""}`}
>
<div
className={`scard compact ${working && isVisible ? "cworking" : ""}`}
Expand Down
2 changes: 2 additions & 0 deletions src/stores/settingsStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,8 @@ const settingUpdaters: {
lazy_stream_close: (value) =>
commands.changeLazyStreamCloseSetting(value as boolean),
overlay_style: (value) => commands.changeOverlayStyleSetting(value as string),
monochrome_overlay: (value) =>
commands.changeMonochromeOverlaySetting(value as boolean),
vad_enabled: (value) => commands.changeVadEnabledSetting(value as boolean),
vad_backend: async (value) => {
const result = await commands.changeVadBackendSetting(value as VadBackend);
Expand Down