feat: actually implement system audio + refactor

This commit is contained in:
Owen Gretzinger
2025-07-10 16:45:19 -04:00
parent 417d751d7a
commit 297bb40b89
21 changed files with 1307 additions and 615 deletions
@@ -0,0 +1,35 @@
import Foundation
import SwiftUI
@MainActor
class MeetingListViewModel: ObservableObject {
@Published var meetings: [Meeting] = []
@Published var isLoading = false
@Published var errorMessage: String?
init() {
loadMeetings()
}
func loadMeetings() {
isLoading = true
errorMessage = nil
DispatchQueue.main.async { [weak self] in
self?.meetings = LocalStorageManager.shared.loadMeetings()
self?.isLoading = false
}
}
func deleteMeeting(_ meeting: Meeting) {
meetings.removeAll { $0.id == meeting.id }
_ = LocalStorageManager.shared.deleteMeeting(meeting)
}
func createNewMeeting() -> Meeting {
let newMeeting = Meeting()
meetings.insert(newMeeting, at: 0)
_ = LocalStorageManager.shared.saveMeeting(newMeeting)
return newMeeting
}
}
@@ -0,0 +1,85 @@
import Foundation
import SwiftUI
import Combine
@MainActor
class MeetingViewModel: ObservableObject {
@Published var meeting: Meeting
@Published var isGeneratingNotes = false
@Published var errorMessage: String?
@Published var isRecording = false
private let audioManager = AudioManager()
private var cancellables = Set<AnyCancellable>()
init(meeting: Meeting = Meeting()) {
self.meeting = meeting
// Update meeting transcript when audio manager transcript changes
audioManager.$transcript
.sink { [weak self] newTranscript in
self?.meeting.transcript = newTranscript
}
.store(in: &cancellables)
// Update isRecording when audio manager recording state changes
audioManager.$isRecording
.sink { [weak self] isRecording in
self?.isRecording = isRecording
}
.store(in: &cancellables)
}
var captureSystemAudio: Bool {
get { audioManager.captureSystemAudio }
set { audioManager.captureSystemAudio = newValue }
}
func startRecording() {
audioManager.startRecording()
}
func stopRecording() {
audioManager.stopRecording()
saveMeeting()
}
func generateNotes() async {
isGeneratingNotes = true
errorMessage = nil
do {
// Load settings for generation
let userBlurb = KeychainHelper.shared.get(forKey: "userBlurb") ?? ""
let systemPrompt = KeychainHelper.shared.get(forKey: "systemPrompt") ?? Settings.defaultSystemPrompt
// Generate notes
meeting.generatedNotes = try await NotesGenerator.shared.generateNotes(
transcript: meeting.transcript,
userNotes: meeting.userNotes,
userBlurb: userBlurb,
systemPrompt: systemPrompt
)
saveMeeting()
} catch {
errorMessage = "Failed to generate notes: \(error.localizedDescription)"
}
isGeneratingNotes = false
}
func saveMeeting() {
_ = LocalStorageManager.shared.saveMeeting(meeting)
}
func copyTranscript() {
NSPasteboard.general.clearContents()
NSPasteboard.general.setString(meeting.transcript, forType: .string)
}
func copyNotes() {
NSPasteboard.general.clearContents()
NSPasteboard.general.setString(meeting.generatedNotes, forType: .string)
}
}
@@ -0,0 +1,51 @@
import Foundation
import SwiftUI
@MainActor
class SettingsViewModel: ObservableObject {
@Published var settings: Settings
@Published var isSaving = false
@Published var saveSuccessful = false
@Published var errorMessage: String?
init() {
self.settings = Settings()
loadSettings()
}
func loadSettings() {
// Load API keys from Keychain
settings.deepgramKey = KeychainHelper.shared.get(forKey: "deepgramKey") ?? ""
settings.openAIKey = KeychainHelper.shared.get(forKey: "openAIKey") ?? ""
settings.userBlurb = KeychainHelper.shared.get(forKey: "userBlurb") ?? ""
settings.systemPrompt = KeychainHelper.shared.get(forKey: "systemPrompt") ?? Settings.defaultSystemPrompt
}
func saveSettings() {
isSaving = true
errorMessage = nil
saveSuccessful = false
// Save to Keychain
let deepgramSaved = KeychainHelper.shared.save(settings.deepgramKey, forKey: "deepgramKey")
let openAISaved = KeychainHelper.shared.save(settings.openAIKey, forKey: "openAIKey")
let blurbSaved = KeychainHelper.shared.save(settings.userBlurb, forKey: "userBlurb")
let promptSaved = KeychainHelper.shared.save(settings.systemPrompt, forKey: "systemPrompt")
if deepgramSaved && openAISaved && blurbSaved && promptSaved {
saveSuccessful = true
// Show success briefly
DispatchQueue.main.asyncAfter(deadline: .now() + 2) { [weak self] in
self?.saveSuccessful = false
}
} else {
errorMessage = "Failed to save some settings. Please try again."
}
isSaving = false
}
func resetToDefaults() {
settings.systemPrompt = Settings.defaultSystemPrompt
}
}