Skip to content

Commit ec3a828

Browse files
committed
feat: refine recording intelligence and diagnostics
1 parent cf61c38 commit ec3a828

13 files changed

Lines changed: 1010 additions & 391 deletions

LiveTranscriber/ContentView.swift

Lines changed: 21 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -177,16 +177,27 @@ struct ContentView: View {
177177
SettingsView(transcriber: transcriber, recordingStore: recordingStore)
178178
}
179179

180-
if #available(iOS 27.0, *) {
181-
Tab(
182-
String(localized: L10n.App.transcribeTab),
183-
systemImage: "waveform.and.mic",
184-
value: AppTab.transcribe,
185-
role: .prominent
186-
) {
187-
transcriptionTab
180+
#if HAS_IOS27_SDK
181+
if #available(iOS 27.0, *) {
182+
Tab(
183+
String(localized: L10n.App.transcribeTab),
184+
systemImage: "waveform.and.mic",
185+
value: AppTab.transcribe,
186+
role: .prominent
187+
) {
188+
transcriptionTab
189+
}
190+
} else {
191+
Tab(
192+
String(localized: L10n.App.transcribeTab),
193+
systemImage: "waveform.and.mic",
194+
value: AppTab.transcribe,
195+
role: .search
196+
) {
197+
transcriptionTab
198+
}
188199
}
189-
} else {
200+
#else
190201
Tab(
191202
String(localized: L10n.App.transcribeTab),
192203
systemImage: "waveform.and.mic",
@@ -195,7 +206,7 @@ struct ContentView: View {
195206
) {
196207
transcriptionTab
197208
}
198-
}
209+
#endif
199210
}
200211
.tabBarMinimizeBehavior(.onScrollDown)
201212
.modifier(

LiveTranscriber/SettingsView.swift

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,8 @@ struct SettingsView: View {
7171
@State private var isGeminiCloudEnabled = GeminiCloudConfiguration.isEnabled
7272
@AppStorage(OnboardingState.completedDefaultsKey) private var hasCompletedOnboarding = true
7373
@AppStorage(RecordingSummaryProvider.selectedDefaultsKey) private var selectedSummaryProviderRawValue = RecordingSummaryProvider.automatic.rawValue
74+
@AppStorage(IntelligenceOutputLanguage.selectedDefaultsKey)
75+
private var selectedIntelligenceOutputLanguageRawValue = IntelligenceOutputLanguage.followApp.rawValue
7476
@AppStorage(Qwen3ASRDeveloperConfiguration.streamingLongAudioDefaultsKey) private var isQwen3ASRStreamingLongAudioEnabled = false
7577
@AppStorage(ManualGeminiDeveloperConfiguration.enabledDefaultsKey) private var isManualGeminiEnabled = false
7678
@AppStorage(MOSSDecoderSegmentDuration.defaultsKey) private var mossDecoderSegmentDurationSeconds = MOSSDecoderSegmentDuration.defaultValue.rawValue
@@ -93,6 +95,10 @@ struct SettingsView: View {
9395
RecordingSummaryProvider(rawValue: selectedSummaryProviderRawValue) ?? .automatic
9496
}
9597

98+
private var selectedIntelligenceOutputLanguage: IntelligenceOutputLanguage {
99+
IntelligenceOutputLanguage(rawValue: selectedIntelligenceOutputLanguageRawValue) ?? .followApp
100+
}
101+
96102
private var selectedMOSSDecoderSegmentDuration: MOSSDecoderSegmentDuration {
97103
MOSSDecoderSegmentDuration(rawValue: mossDecoderSegmentDurationSeconds) ?? .defaultValue
98104
}
@@ -594,6 +600,41 @@ struct SettingsView: View {
594600
)
595601
}
596602

603+
SettingsSection(
604+
titleResource: L10n.Intelligence.outputLanguageSectionTitle,
605+
systemImage: "globe",
606+
tint: AppTheme.info
607+
) {
608+
Menu {
609+
ForEach(IntelligenceOutputLanguage.allCases) { language in
610+
Button {
611+
HapticFeedback.play(.menuSelection)
612+
selectedIntelligenceOutputLanguageRawValue = language.rawValue
613+
IntelligenceOutputLanguage.select(language)
614+
} label: {
615+
Label(
616+
language.displayName,
617+
systemImage: language == selectedIntelligenceOutputLanguage ? "checkmark" : "globe"
618+
)
619+
}
620+
}
621+
} label: {
622+
SettingsPickerRow(
623+
icon: "character.bubble",
624+
titleResource: L10n.Intelligence.outputLanguage,
625+
value: selectedIntelligenceOutputLanguage.settingsValue,
626+
tint: AppTheme.info
627+
)
628+
}
629+
.buttonStyle(.plain)
630+
631+
SettingsStatusRow(
632+
icon: "text.bubble",
633+
textResource: L10n.Intelligence.outputLanguageDescription,
634+
tint: AppTheme.info
635+
)
636+
}
637+
597638
SettingsSection(titleResource: L10n.Intelligence.appleModelTitle, systemImage: "sparkles", tint: appleAvailabilityTint) {
598639
SettingsMetricRow(
599640
icon: "iphone",

LiveTranscriber/TranscriptionView.swift

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,17 @@
11
import Combine
22
import CoreLocation
33
import MapKit
4+
import OSLog
45
import SwiftUI
56
import TranscriberDomain
67
import Translation
78
import UIKit
89

10+
private let recordingSaveUILogger = Logger(
11+
subsystem: "com.iamwilliamli.LiveTranscriber",
12+
category: "RecordingSave"
13+
)
14+
915
struct TranscriptionView: View {
1016
@Environment(\.colorScheme) private var colorScheme
1117
@Environment(\.accessibilityReduceMotion) private var reduceMotion
@@ -1001,6 +1007,7 @@ struct TranscriptionView: View {
10011007
return
10021008
}
10031009

1010+
let saveAttemptID = UUID().uuidString
10041011
let selectedLanguage = pendingRecordingLanguageOptions.first {
10051012
$0.id == pendingRecordingLanguageID
10061013
} ?? TranscriptionLanguage(id: pendingRecordingLanguageID).baseLanguage
@@ -1010,7 +1017,16 @@ struct TranscriptionView: View {
10101017

10111018
isSavingPendingRecording = true
10121019
recordingSaveErrorMessage = nil
1020+
recordingSaveUILogger.notice(
1021+
"ui_save_started attempt=\(saveAttemptID, privacy: .public) durationSeconds=\(resolvedDraft.durationSeconds, privacy: .public) transcriptLines=\(resolvedDraft.lines.count, privacy: .public) audioExtension=\(resolvedDraft.audioURL.pathExtension, privacy: .public)"
1022+
)
10131023
Task {
1024+
defer {
1025+
isSavingPendingRecording = false
1026+
recordingSaveUILogger.notice(
1027+
"ui_save_state_reset attempt=\(saveAttemptID, privacy: .public)"
1028+
)
1029+
}
10141030
let location = pendingRecordingIncludesLocation ? locationProvider.recordingLocation : nil
10151031
let intelligence = pendingRecordingIntelligence.map { pendingIntelligence in
10161032
RecordingIntelligence(
@@ -1027,15 +1043,22 @@ struct TranscriptionView: View {
10271043
projectName: nil,
10281044
categoryName: pendingRecordingCategory,
10291045
keyPoints: pendingRecordingKeyPoints,
1030-
location: location
1046+
location: location,
1047+
diagnosticAttemptID: saveAttemptID
10311048
) {
1049+
recordingSaveUILogger.notice(
1050+
"ui_save_returned attempt=\(saveAttemptID, privacy: .public) result=success"
1051+
)
10321052
RecordingCategoryCatalog.register(pendingRecordingCategory)
10331053
showSavedRecordingBanner(fileName: saved.displayFileName)
10341054
transcriber.clearTranscript()
10351055
self.pendingRecordingSave = nil
10361056
pendingRecordingIntelligence = nil
10371057
HapticFeedback.play(.recordingSaved)
10381058
} else {
1059+
recordingSaveUILogger.error(
1060+
"ui_save_returned attempt=\(saveAttemptID, privacy: .public) result=failure"
1061+
)
10391062
let fallbackMessage = localized(L10n.Transcription.saveRecordingFailedMessage)
10401063
if let reason = recordingStore.lastRecordingSaveErrorMessage?
10411064
.trimmingCharacters(in: .whitespacesAndNewlines),
@@ -1046,7 +1069,6 @@ struct TranscriptionView: View {
10461069
}
10471070
HapticFeedback.play(.failure)
10481071
}
1049-
isSavingPendingRecording = false
10501072
}
10511073
}
10521074

@@ -3156,7 +3178,7 @@ private struct DashboardPlaceRecordingsView: View {
31563178
placeHeader
31573179

31583180
VStack(alignment: .leading, spacing: 4) {
3159-
Text(L10n.Dashboard.voiceStampCollection)
3181+
Text(L10n.Dashboard.placeRecordings)
31603182
.font(.redditSans(.headline, weight: .semibold))
31613183
.padding(.bottom, 6)
31623184

0 commit comments

Comments
 (0)