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