Open saved session: visible progress + clear errors (no silent no-op)
The status line only rendered inside the last-in-memory-session block, so 'Open saved session' processed invisibly — looked like nothing happened. Now: the transcript status (with a spinner) is always shown, the processing(0,0) reconcile phase reads 'Working… (this can take a few minutes)', and invalid picks surface an alert (not a recorded session / already processing / unreadable transcript) instead of doing nothing.
This commit is contained in:
@@ -444,13 +444,16 @@ final class SessionController: ObservableObject {
|
|||||||
if let folder = lastSession?.folder { openEditor(folder: folder) }
|
if let folder = lastSession?.folder { openEditor(folder: folder) }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Open the editor for any session folder that has a `speakers.json`.
|
/// Open the editor for any session folder that has a `speakers.json`. Returns
|
||||||
private func openEditor(folder: URL) {
|
/// false if it couldn't load (no/empty transcript).
|
||||||
|
@discardableResult
|
||||||
|
private func openEditor(folder: URL) -> Bool {
|
||||||
guard let model = RecapEditModel(folder: folder, voiceprints: voiceprints,
|
guard let model = RecapEditModel(folder: folder, voiceprints: voiceprints,
|
||||||
baseURL: settings.backendBaseURL, skipTLS: settings.skipTLSVerification,
|
baseURL: settings.backendBaseURL, skipTLS: settings.skipTLSVerification,
|
||||||
templates: settings.recapTemplates, defaultTemplateId: settings.defaultTemplateId)
|
templates: settings.recapTemplates, defaultTemplateId: settings.defaultTemplateId)
|
||||||
else { return }
|
else { return false }
|
||||||
EditorWindow.shared.show(model: model)
|
EditorWindow.shared.show(model: model)
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Pick any past session folder and open it: edit it if already transcribed,
|
/// 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 }
|
guard panel.runModal() == .OK, let folder = panel.url else { return }
|
||||||
let fm = FileManager.default
|
let fm = FileManager.default
|
||||||
if fm.fileExists(atPath: folder.appendingPathComponent("speakers.json").path) {
|
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
|
return
|
||||||
}
|
}
|
||||||
// Not transcribed yet — needs the raw tracks to (re)process.
|
// Not transcribed yet — needs the raw tracks to (re)process.
|
||||||
let mic = folder.appendingPathComponent("mic.wav")
|
let mic = folder.appendingPathComponent("mic.wav")
|
||||||
let sys = folder.appendingPathComponent("system.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)
|
transcriptStatus = .processing(0, 1)
|
||||||
recapURL = nil
|
recapURL = nil
|
||||||
let selfName = settings.selfName
|
let selfName = settings.selfName
|
||||||
@@ -500,6 +512,15 @@ final class SessionController: ObservableObject {
|
|||||||
folder.lastPathComponent.split(separator: "_").last.map(String.init) ?? "manual"
|
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) {
|
private func fail(_ message: String) {
|
||||||
recorder = nil
|
recorder = nil
|
||||||
visualCapture = nil // recorder.start() failed before visual started; nothing running
|
visualCapture = nil // recorder.start() failed before visual started; nothing running
|
||||||
|
|||||||
@@ -99,14 +99,19 @@ struct MenuBarView: View {
|
|||||||
}
|
}
|
||||||
Spacer()
|
Spacer()
|
||||||
}
|
}
|
||||||
if !transcriptText.isEmpty {
|
|
||||||
Text(transcriptText).font(.caption).foregroundStyle(transcriptColor)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
HStack(spacing: 6) {
|
||||||
Button("Open saved session…") { session.openSavedSession() }
|
Button("Open saved session…") { session.openSavedSession() }
|
||||||
.buttonStyle(.link).font(.caption)
|
.buttonStyle(.link).font(.caption)
|
||||||
.disabled(transcriptProcessing)
|
.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 {
|
private var transcriptText: String {
|
||||||
switch session.transcriptStatus {
|
switch session.transcriptStatus {
|
||||||
case .idle: return ""
|
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 .processing(let d, let t): return "Transcribing… chunk \(d)/\(t)"
|
||||||
case .done(let s, let seg): return "Transcript ready · \(s) speakers · \(seg) segments"
|
case .done(let s, let seg): return "Transcript ready · \(s) speakers · \(seg) segments"
|
||||||
case .failed(let m): return "Transcript failed: \(m)"
|
case .failed(let m): return "Transcript failed: \(m)"
|
||||||
|
|||||||
Reference in New Issue
Block a user