Files
ten31-transcripts/Ten31Transcripts/UI/SettingsView.swift
T
Grant Gilliam 8f82e9c0a1 Make adapter toggles actually gate screen-reading
The Settings "Adapters" toggles wrote adapterEnabled but nothing in the capture
path ever read it, so flipping one off did nothing — and the caption still said
"Inert in Phase 0". The adapters (Zoom/Teams/Signal/Meet) are all live now.

SessionController.startVisual now skips visual capture when the detected app's
adapter is toggled off (records audio-only; transcription still runs). Update the
section caption to describe the real behavior.
2026-06-08 13:30:31 -05:00

111 lines
4.8 KiB
Swift
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import SwiftUI
import AppKit
/// Settings panel (pushed from the menu-bar panel).
struct SettingsView: View {
@EnvironmentObject private var settings: AppSettings
var body: some View {
Form {
Section("Your name") {
TextField("Your name", text: $settings.selfName)
.textFieldStyle(.roundedBorder)
if isDefaultName {
Label("Still set to the default. Enter your real name so your own voice is labeled correctly — and so the AI never gives your name to someone else.",
systemImage: "exclamationmark.triangle.fill")
.font(.caption)
.foregroundStyle(.orange)
} else {
Text("Labels your microphone channel as you in every transcript, and reserves this name so its never assigned to another speaker.")
.font(.caption)
.foregroundStyle(.secondary)
}
}
Section("SparkControl backend") {
TextField("Base URL", text: $settings.backendBaseURL)
.textFieldStyle(.roundedBorder)
Toggle("Skip TLS verification (self-signed cert)",
isOn: $settings.skipTLSVerification)
}
Section("Call detection") {
Toggle("Auto-record when a call is detected", isOn: $settings.autoRecordOnDetection)
Text("Detects Zoom, Teams, Signal, and Google Meet (any browser).")
.font(.caption)
.foregroundStyle(.secondary)
}
Section("Transcription") {
Toggle("Auto-send recordings to backend", isOn: $settings.autoSendOnStop)
Toggle("Reconcile speakers (merge splits + name from content)", isOn: $settings.reconcileSpeakers)
Toggle("Build readable recap (topics + highlights)", isOn: $settings.recapEnabled)
HStack {
Picker("Default recap template", selection: $settings.defaultTemplateId) {
ForEach(settings.recapTemplates) { Text($0.name).tag($0.id) }
}
Button("Manage…") { TemplatesWindow.shared.show(settings: settings) }
}
Text("Auto-send transcribes on stop; the recap writes transcript.md + recap.html. Templates define the takeaways categories per meeting type.")
.font(.caption)
.foregroundStyle(.secondary)
}
Section("Output") {
HStack {
Text(settings.outputFolderPath)
.lineLimit(1)
.truncationMode(.middle)
.foregroundStyle(.secondary)
Spacer()
Button("Choose…", action: chooseFolder)
}
}
Section("Adapters") {
Text("Screen-reading for active-speaker cues. Turn one off to record that app audio-only — transcription still runs, but speakers arent identified from the screen.")
.font(.caption)
.foregroundStyle(.secondary)
ForEach(AppSettings.adapterKeys, id: \.key) { adapter in
Toggle(adapter.label, isOn: binding(for: adapter.key))
}
}
}
.formStyle(.grouped)
.frame(width: 320)
.navigationTitle("Settings")
}
/// True while the user still has the placeholder name drives the inline nudge.
private var isDefaultName: Bool {
let n = settings.selfName.trimmingCharacters(in: .whitespacesAndNewlines)
return n.isEmpty || n.caseInsensitiveCompare("Me") == .orderedSame
}
private func binding(for key: String) -> Binding<Bool> {
Binding(
get: { settings.adapterEnabled[key] ?? true },
set: { settings.adapterEnabled[key] = $0 }
)
}
private func chooseFolder() {
let panel = NSOpenPanel()
panel.canChooseDirectories = true
panel.canChooseFiles = false
panel.allowsMultipleSelection = false
panel.prompt = "Choose"
panel.directoryURL = settings.outputFolderURL
// The app is a menu-bar accessory and this is invoked from the transient
// MenuBarExtra(.window) popover. Use the async begin(...) API rather than
// runModal() a nested modal loop can let the popover dismiss the panel
// out from under it. Activate first so the panel comes to the front.
NSApp.activate(ignoringOtherApps: true)
panel.begin { response in
guard response == .OK, let url = panel.url else { return }
settings.outputFolderPath = url.path
}
}
}