from __future__ import annotations
import gi
from config_ui_runtime import RUNTIME_MODE_EXPERT, RUNTIME_MODE_MANAGED
from languages import COMMON_STT_LANGUAGE_OPTIONS
gi.require_version("Gtk", "3.0")
from gi.repository import Gtk # type: ignore[import-not-found]
def _page_box() -> Gtk.Box:
box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=10)
box.set_margin_start(14)
box.set_margin_end(14)
box.set_margin_top(14)
box.set_margin_bottom(14)
return box
def build_general_page(window) -> Gtk.Widget:
grid = Gtk.Grid(column_spacing=12, row_spacing=10)
grid.set_margin_start(14)
grid.set_margin_end(14)
grid.set_margin_top(14)
grid.set_margin_bottom(14)
hotkey_label = Gtk.Label(label="Trigger hotkey")
hotkey_label.set_xalign(0.0)
window._hotkey_entry = Gtk.Entry()
window._hotkey_entry.set_placeholder_text("Super+m")
window._hotkey_entry.connect("changed", lambda *_: window._validate_hotkey())
grid.attach(hotkey_label, 0, 0, 1, 1)
grid.attach(window._hotkey_entry, 1, 0, 1, 1)
window._hotkey_error = Gtk.Label(label="")
window._hotkey_error.set_xalign(0.0)
window._hotkey_error.set_line_wrap(True)
grid.attach(window._hotkey_error, 1, 1, 1, 1)
backend_label = Gtk.Label(label="Text injection")
backend_label.set_xalign(0.0)
window._backend_combo = Gtk.ComboBoxText()
window._backend_combo.append("clipboard", "Clipboard paste (recommended)")
window._backend_combo.append("injection", "Simulated typing")
grid.attach(backend_label, 0, 2, 1, 1)
grid.attach(window._backend_combo, 1, 2, 1, 1)
window._remove_clipboard_check = Gtk.CheckButton(
label="Remove transcription from clipboard after paste"
)
window._remove_clipboard_check.set_hexpand(True)
grid.attach(window._remove_clipboard_check, 1, 3, 1, 1)
language_label = Gtk.Label(label="Transcription language")
language_label.set_xalign(0.0)
window._language_combo = Gtk.ComboBoxText()
for code, label in COMMON_STT_LANGUAGE_OPTIONS:
window._language_combo.append(code, label)
grid.attach(language_label, 0, 4, 1, 1)
grid.attach(window._language_combo, 1, 4, 1, 1)
profile_label = Gtk.Label(label="Profile")
profile_label.set_xalign(0.0)
window._profile_combo = Gtk.ComboBoxText()
window._profile_combo.append("default", "Default")
window._profile_combo.append("fast", "Fast (lower latency)")
window._profile_combo.append("polished", "Polished")
grid.attach(profile_label, 0, 5, 1, 1)
grid.attach(window._profile_combo, 1, 5, 1, 1)
return grid
def build_audio_page(window) -> Gtk.Widget:
box = _page_box()
input_label = Gtk.Label(label="Input device")
input_label.set_xalign(0.0)
box.pack_start(input_label, False, False, 0)
window._mic_combo = Gtk.ComboBoxText()
window._mic_combo.append("", "System default")
for device in window._devices:
window._mic_combo.append(
str(device["index"]),
f"{device['index']}: {device['name']}",
)
box.pack_start(window._mic_combo, False, False, 0)
test_button = Gtk.Button(label="Test microphone")
test_button.connect("clicked", lambda *_: window._on_test_microphone())
box.pack_start(test_button, False, False, 0)
window._mic_status = Gtk.Label(label="")
window._mic_status.set_xalign(0.0)
window._mic_status.set_line_wrap(True)
box.pack_start(window._mic_status, False, False, 0)
return box
def build_advanced_page(window) -> Gtk.Widget:
box = _page_box()
window._strict_startup_check = Gtk.CheckButton(
label="Fail fast on startup validation errors"
)
box.pack_start(window._strict_startup_check, False, False, 0)
safety_title = Gtk.Label()
safety_title.set_markup("Output safety")
safety_title.set_xalign(0.0)
box.pack_start(safety_title, False, False, 0)
window._safety_enabled_check = Gtk.CheckButton(
label="Enable fact-preservation guard (recommended)"
)
window._safety_enabled_check.connect(
"toggled",
lambda *_: window._on_safety_guard_toggled(),
)
box.pack_start(window._safety_enabled_check, False, False, 0)
window._safety_strict_check = Gtk.CheckButton(
label="Strict mode: reject output when facts are changed"
)
box.pack_start(window._safety_strict_check, False, False, 0)
runtime_title = Gtk.Label()
runtime_title.set_markup("Runtime management")
runtime_title.set_xalign(0.0)
box.pack_start(runtime_title, False, False, 0)
runtime_copy = Gtk.Label(
label=(
"Aman-managed mode handles the canonical editor model lifecycle for you. "
"Expert mode keeps Aman open-source friendly by letting you use custom Whisper paths."
)
)
runtime_copy.set_xalign(0.0)
runtime_copy.set_line_wrap(True)
box.pack_start(runtime_copy, False, False, 0)
mode_label = Gtk.Label(label="Runtime mode")
mode_label.set_xalign(0.0)
box.pack_start(mode_label, False, False, 0)
window._runtime_mode_combo = Gtk.ComboBoxText()
window._runtime_mode_combo.append(
RUNTIME_MODE_MANAGED,
"Aman-managed (recommended)",
)
window._runtime_mode_combo.append(
RUNTIME_MODE_EXPERT,
"Expert mode (custom Whisper path)",
)
window._runtime_mode_combo.connect(
"changed",
lambda *_: window._on_runtime_mode_changed(user_initiated=True),
)
box.pack_start(window._runtime_mode_combo, False, False, 0)
window._runtime_status_label = Gtk.Label(label="")
window._runtime_status_label.set_xalign(0.0)
window._runtime_status_label.set_line_wrap(True)
box.pack_start(window._runtime_status_label, False, False, 0)
window._expert_expander = Gtk.Expander(label="Expert options")
window._expert_expander.set_expanded(False)
box.pack_start(window._expert_expander, False, False, 0)
expert_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=8)
expert_box.set_margin_start(10)
expert_box.set_margin_end(10)
expert_box.set_margin_top(8)
expert_box.set_margin_bottom(8)
window._expert_expander.add(expert_box)
expert_warning = Gtk.InfoBar()
expert_warning.set_show_close_button(False)
expert_warning.set_message_type(Gtk.MessageType.WARNING)
warning_label = Gtk.Label(
label=(
"Expert mode is best-effort and may require manual troubleshooting. "
"Aman-managed mode is the canonical supported path."
)
)
warning_label.set_xalign(0.0)
warning_label.set_line_wrap(True)
expert_warning.get_content_area().pack_start(warning_label, True, True, 0)
expert_box.pack_start(expert_warning, False, False, 0)
window._allow_custom_models_check = Gtk.CheckButton(
label="Allow custom local model paths"
)
window._allow_custom_models_check.connect(
"toggled",
lambda *_: window._on_runtime_widgets_changed(),
)
expert_box.pack_start(window._allow_custom_models_check, False, False, 0)
whisper_model_path_label = Gtk.Label(label="Custom Whisper model path")
whisper_model_path_label.set_xalign(0.0)
expert_box.pack_start(whisper_model_path_label, False, False, 0)
window._whisper_model_path_entry = Gtk.Entry()
window._whisper_model_path_entry.connect(
"changed",
lambda *_: window._on_runtime_widgets_changed(),
)
expert_box.pack_start(window._whisper_model_path_entry, False, False, 0)
window._runtime_error = Gtk.Label(label="")
window._runtime_error.set_xalign(0.0)
window._runtime_error.set_line_wrap(True)
expert_box.pack_start(window._runtime_error, False, False, 0)
path_label = Gtk.Label(label="Config path")
path_label.set_xalign(0.0)
box.pack_start(path_label, False, False, 0)
path_entry = Gtk.Entry()
path_entry.set_editable(False)
path_entry.set_text(str(window._config_path))
box.pack_start(path_entry, False, False, 0)
note = Gtk.Label(
label=(
"Tip: after editing the file directly, use Reload Config from the tray to apply changes."
)
)
note.set_xalign(0.0)
note.set_line_wrap(True)
box.pack_start(note, False, False, 0)
return box
def build_help_page(window, *, present_about_dialog) -> Gtk.Widget:
box = _page_box()
help_text = Gtk.Label(
label=(
"Usage:\n"
"- Press your hotkey to start recording.\n"
"- Press the hotkey again to stop and process.\n"
"- Press Esc while recording to cancel.\n\n"
"Supported path:\n"
"- Daily use runs through the tray and user service.\n"
"- Aman-managed mode (recommended) handles model lifecycle for you.\n"
"- Expert mode keeps custom Whisper paths available for advanced users.\n\n"
"Recovery:\n"
"- Use Run Diagnostics from the tray for a deeper self-check.\n"
"- If that is not enough, run aman doctor, then aman self-check.\n"
"- Next escalations are journalctl --user -u aman and aman run --verbose.\n\n"
"Safety tips:\n"
"- Keep fact guard enabled to prevent accidental name/number changes.\n"
"- Strict safety blocks output on fact violations."
)
)
help_text.set_xalign(0.0)
help_text.set_line_wrap(True)
box.pack_start(help_text, False, False, 0)
about_button = Gtk.Button(label="Open About Dialog")
about_button.connect(
"clicked",
lambda *_: present_about_dialog(window._dialog),
)
box.pack_start(about_button, False, False, 0)
return box
def build_about_page(window, *, present_about_dialog) -> Gtk.Widget:
box = _page_box()
title = Gtk.Label()
title.set_markup("Aman")
title.set_xalign(0.0)
box.pack_start(title, False, False, 0)
subtitle = Gtk.Label(
label="Local amanuensis for X11 desktop dictation and rewriting."
)
subtitle.set_xalign(0.0)
subtitle.set_line_wrap(True)
box.pack_start(subtitle, False, False, 0)
about_button = Gtk.Button(label="About Aman")
about_button.connect(
"clicked",
lambda *_: present_about_dialog(window._dialog),
)
box.pack_start(about_button, False, False, 0)
return box