diff --git a/Ten31Transcripts/Session/SessionController.swift b/Ten31Transcripts/Session/SessionController.swift index 06c75a2..f12e045 100644 --- a/Ten31Transcripts/Session/SessionController.swift +++ b/Ten31Transcripts/Session/SessionController.swift @@ -444,13 +444,16 @@ final class SessionController: ObservableObject { if let folder = lastSession?.folder { openEditor(folder: folder) } } - /// Open the editor for any session folder that has a `speakers.json`. - private func openEditor(folder: URL) { + /// Open the editor for any session folder that has a `speakers.json`. Returns + /// false if it couldn't load (no/empty transcript). + @discardableResult + private func openEditor(folder: URL) -> Bool { guard let model = RecapEditModel(folder: folder, voiceprints: voiceprints, baseURL: settings.backendBaseURL, skipTLS: settings.skipTLSVerification, templates: settings.recapTemplates, defaultTemplateId: settings.defaultTemplateId) - else { return } + else { return false } EditorWindow.shared.show(model: model) + return true } /// Pick any past session folder and open it: edit it if already transcribed, @@ -467,13 +470,22 @@ final class SessionController: ObservableObject { guard panel.runModal() == .OK, let folder = panel.url else { return } let fm = FileManager.default if fm.fileExists(atPath: folder.appendingPathComponent("speakers.json").path) { - openEditor(folder: folder) + if !openEditor(folder: folder) { + Self.alert("Couldn't open this session — its transcript looks empty or unreadable.") + } return } // Not transcribed yet — needs the raw tracks to (re)process. let mic = folder.appendingPathComponent("mic.wav") let sys = folder.appendingPathComponent("system.wav") - guard fm.fileExists(atPath: mic.path), fm.fileExists(atPath: sys.path), !isProcessing else { return } + guard fm.fileExists(atPath: mic.path), fm.fileExists(atPath: sys.path) else { + Self.alert("“\(folder.lastPathComponent)” isn’t a recorded session (no mic.wav / system.wav).") + return + } + guard !isProcessing else { + Self.alert("Already processing a session — let it finish first.") + return + } transcriptStatus = .processing(0, 1) recapURL = nil let selfName = settings.selfName @@ -500,6 +512,15 @@ final class SessionController: ObservableObject { folder.lastPathComponent.split(separator: "_").last.map(String.init) ?? "manual" } + private static func alert(_ message: String) { + let a = NSAlert() + a.messageText = "Open saved session" + a.informativeText = message + a.alertStyle = .informational + NSApp.activate(ignoringOtherApps: true) + a.runModal() + } + private func fail(_ message: String) { recorder = nil visualCapture = nil // recorder.start() failed before visual started; nothing running diff --git a/Ten31Transcripts/UI/MenuBarView.swift b/Ten31Transcripts/UI/MenuBarView.swift index 8da1105..f17d613 100644 --- a/Ten31Transcripts/UI/MenuBarView.swift +++ b/Ten31Transcripts/UI/MenuBarView.swift @@ -99,14 +99,19 @@ struct MenuBarView: View { } Spacer() } - if !transcriptText.isEmpty { - Text(transcriptText).font(.caption).foregroundStyle(transcriptColor) - } } - Button("Open saved session…") { session.openSavedSession() } - .buttonStyle(.link).font(.caption) - .disabled(transcriptProcessing) + HStack(spacing: 6) { + Button("Open saved session…") { session.openSavedSession() } + .buttonStyle(.link).font(.caption) + .disabled(transcriptProcessing) + if transcriptProcessing { ProgressView().controlSize(.small) } + } + + // Always-visible status (covers "Send to backend" AND "Open saved session"). + if !transcriptText.isEmpty { + Text(transcriptText).font(.caption).foregroundStyle(transcriptColor) + } } } @@ -152,6 +157,7 @@ struct MenuBarView: View { private var transcriptText: String { switch session.transcriptStatus { case .idle: return "" + case .processing(_, let t) where t == 0: return "Working… (this can take a few minutes)" case .processing(let d, let t): return "Transcribing… chunk \(d)/\(t)" case .done(let s, let seg): return "Transcript ready · \(s) speakers · \(seg) segments" case .failed(let m): return "Transcript failed: \(m)"