@hasna/recordings 0.2.11 → 0.2.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +349 -69
- package/bun.lock +3 -0
- package/dist/__tests__/helpers/native-fs-guard.d.ts +2 -0
- package/dist/__tests__/helpers/native-fs-guard.d.ts.map +1 -0
- package/dist/cli/index.js +1728 -320
- package/dist/cli/macos-permissions.d.ts +13 -0
- package/dist/cli/macos-permissions.d.ts.map +1 -0
- package/dist/cli/options.d.ts +12 -0
- package/dist/cli/options.d.ts.map +1 -1
- package/dist/db/pg-migrations.d.ts.map +1 -1
- package/dist/db/recordings.d.ts +2 -1
- package/dist/db/recordings.d.ts.map +1 -1
- package/dist/db/remote-storage.d.ts +5 -1
- package/dist/db/remote-storage.d.ts.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +628 -147
- package/dist/lib/bun-runtime.d.ts +11 -0
- package/dist/lib/bun-runtime.d.ts.map +1 -0
- package/dist/lib/config.d.ts +8 -1
- package/dist/lib/config.d.ts.map +1 -1
- package/dist/lib/enhancer.d.ts +10 -2
- package/dist/lib/enhancer.d.ts.map +1 -1
- package/dist/lib/install-maintenance.d.ts +24 -0
- package/dist/lib/install-maintenance.d.ts.map +1 -0
- package/dist/lib/machine.d.ts +2 -0
- package/dist/lib/machine.d.ts.map +1 -0
- package/dist/lib/recorder.d.ts +2 -1
- package/dist/lib/recorder.d.ts.map +1 -1
- package/dist/lib/recording-create-identity.d.ts +13 -0
- package/dist/lib/recording-create-identity.d.ts.map +1 -0
- package/dist/lib/release-install-policy.d.ts +31 -0
- package/dist/lib/release-install-policy.d.ts.map +1 -0
- package/dist/lib/transcriber.d.ts.map +1 -1
- package/dist/mcp/index.d.ts.map +1 -1
- package/dist/mcp/index.js +977 -194
- package/dist/sdk/index.js +5 -1
- package/dist/sdk/v1.generated.d.ts +4 -0
- package/dist/sdk/v1.generated.d.ts.map +1 -1
- package/dist/server/cloud-config.d.ts +9 -0
- package/dist/server/cloud-config.d.ts.map +1 -0
- package/dist/server/cloud-readiness.d.ts +10 -0
- package/dist/server/cloud-readiness.d.ts.map +1 -0
- package/dist/server/cloud.d.ts +9 -18
- package/dist/server/cloud.d.ts.map +1 -1
- package/dist/server/index.js +1654 -304
- package/dist/server/migrate-command.d.ts +11 -0
- package/dist/server/migrate-command.d.ts.map +1 -0
- package/dist/server/openapi.d.ts +22 -0
- package/dist/server/openapi.d.ts.map +1 -1
- package/dist/server/repo.d.ts +8 -1
- package/dist/server/repo.d.ts.map +1 -1
- package/dist/server/serve.d.ts +6 -1
- package/dist/server/serve.d.ts.map +1 -1
- package/dist/server/v1.d.ts.map +1 -1
- package/dist/storage.js +515 -80
- package/dist/store.d.ts +3 -1
- package/dist/store.d.ts.map +1 -1
- package/dist/types/index.d.ts +10 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/version.d.ts +1 -1
- package/package.json +7 -4
- package/packaging/macos/Empty.entitlements +5 -0
- package/packaging/macos/Library/LaunchDaemons/com.hasna.recordings.updater.plist +34 -0
- package/packaging/macos/Verifier.entitlements +10 -0
- package/packaging/macos/artifact-verifier.sb +33 -0
- package/packaging/macos/build_release_pkg.sh +872 -0
- package/packaging/macos/managed_bootstrap.sh +623 -0
- package/packaging/macos/pkgutil_fingerprint.awk +37 -0
- package/packaging/macos/release_lifecycle.ts +463 -0
- package/packaging/macos/scripts/postinstall +565 -0
- package/packaging/macos/scripts/preinstall +297 -0
- package/scripts/build_companion_cli.sh +337 -0
- package/scripts/build_native_fs_guard.sh +59 -0
- package/scripts/generate-sdk.ts +19 -1
- package/scripts/install_macos_app.sh +1919 -98
- package/scripts/macos_artifact.ts +5316 -0
- package/scripts/migrate.ts +38 -10
- package/scripts/native/prebuilds/darwin-universal/recordings_fs_guard.node +0 -0
- package/scripts/native/recordings_fs_guard.c +1167 -0
- package/scripts/native_fs_guard.ts +158 -0
- package/scripts/release-guard.ts +20 -1
- package/scripts/resolve_tailscale_cli.sh +389 -0
- package/scripts/smoke_macos_app.sh +573 -0
- package/src/native/Recordings/App/ContentView.swift +83 -0
- package/src/native/Recordings/App/MenuBarStatusView.swift +102 -0
- package/src/native/Recordings/App/RecordWorkspaceView.swift +468 -0
- package/src/native/Recordings/App/RecordingDetailView.swift +138 -0
- package/src/native/Recordings/App/RecordingsApp.swift +367 -44
- package/src/native/Recordings/App/RecordingsListView.swift +130 -0
- package/src/native/Recordings/App/RecordingsStore.swift +206 -0
- package/src/native/Recordings/App/RuntimeSmoke.swift +158 -0
- package/src/native/Recordings/App/SidebarView.swift +212 -0
- package/src/native/Recordings/App/Theme.swift +87 -0
- package/src/native/Recordings/Package.resolved +5 -5
- package/src/native/Recordings/Package.swift +62 -2
- package/src/native/Recordings/RecordingsLib/AccessibilityPromptGate.swift +104 -0
- package/src/native/Recordings/RecordingsLib/BrandAssets.swift +0 -32
- package/src/native/Recordings/RecordingsLib/ChromeSurface.swift +17 -0
- package/src/native/Recordings/RecordingsLib/Info.plist +7 -3
- package/src/native/Recordings/RecordingsLib/MenuBarPresentation.swift +37 -0
- package/src/native/Recordings/RecordingsLib/NativeAppDiagnostics.swift +32 -2
- package/src/native/Recordings/RecordingsLib/NativeMachineIdentity.swift +24 -0
- package/src/native/Recordings/RecordingsLib/NativePCMRecorder.swift +262 -23
- package/src/native/Recordings/RecordingsLib/OpenAIAPIKeyStore.swift +56 -0
- package/src/native/Recordings/RecordingsLib/PermissionRequestLaunchPlan.swift +56 -0
- package/src/native/Recordings/RecordingsLib/ProjectStore.swift +321 -25
- package/src/native/Recordings/RecordingsLib/RealtimeTranscriptionClient.swift +605 -90
- package/src/native/Recordings/RecordingsLib/Recording.swift +141 -0
- package/src/native/Recordings/RecordingsLib/RecordingEngine.swift +3704 -376
- package/src/native/Recordings/RecordingsLib/RecordingStartControlPresentation.swift +29 -0
- package/src/native/Recordings/RecordingsLib/RecordingsCLI.entitlements +10 -0
- package/src/native/Recordings/RecordingsLib/RecordingsCLI.swift +222 -0
- package/src/native/Recordings/RecordingsLib/SettingsView.swift +45 -11
- package/src/native/Recordings/RecordingsLib/SpeechIntent.swift +331 -0
- package/src/native/Recordings/RecordingsLib/SpeechIntentClassifier.swift +232 -0
- package/src/native/Recordings/RecordingsLib/VoiceShortcuts.swift +17 -7
- package/src/native/Recordings/RecordingsTests/CLIRunnerTests.swift +1343 -0
- package/src/native/Recordings/RecordingsTests/MenuBarPresentationTests.swift +98 -0
- package/src/native/Recordings/RecordingsTests/NativeAppDiagnosticsTests.swift +20 -0
- package/src/native/Recordings/RecordingsTests/NativePCMRecorderTests.swift +370 -1
- package/src/native/Recordings/RecordingsTests/PasteTargetTests.swift +987 -1
- package/src/native/Recordings/RecordingsTests/ProjectStoreTests.swift +385 -0
- package/src/native/Recordings/RecordingsTests/RealtimeTranscriptionTests.swift +835 -8
- package/src/native/Recordings/RecordingsTests/RecordingBridgeTests.swift +180 -0
- package/src/native/Recordings/RecordingsTests/RecordingEngineDeliveryTests.swift +760 -0
- package/src/native/Recordings/RecordingsTests/RecordingStartControlPresentationTests.swift +42 -0
- package/src/native/Recordings/RecordingsTests/RecordingStartGateTests.swift +264 -0
- package/src/native/Recordings/RecordingsTests/RecordingStartTimingTests.swift +129 -0
- package/src/native/Recordings/RecordingsTests/SpeechIntentTests.swift +600 -0
- package/src/native/Recordings/RecordingsTests/TranscriptResolutionTests.swift +62 -1
- package/src/native/Recordings/RecordingsTests/TranscriptionResultIdentityTests.swift +21 -0
- package/src/native/Recordings/Updater/BootstrapPreflight/BootstrapPreflightMain.swift +739 -0
- package/src/native/Recordings/Updater/Broker/ActivationRecoveryPolicy.swift +254 -0
- package/src/native/Recordings/Updater/Broker/ApplicationNamespace.swift +463 -0
- package/src/native/Recordings/Updater/Broker/ApplicationProcessQuiescence.swift +55 -0
- package/src/native/Recordings/Updater/Broker/ArtifactIngest.swift +341 -0
- package/src/native/Recordings/Updater/Broker/AtomicActivation.swift +477 -0
- package/src/native/Recordings/Updater/Broker/BrokerMain.swift +624 -0
- package/src/native/Recordings/Updater/Broker/CanonicalTreeCopy.swift +63 -0
- package/src/native/Recordings/Updater/Broker/CodeValidation.swift +561 -0
- package/src/native/Recordings/Updater/Broker/DarwinACLValidator.swift +66 -0
- package/src/native/Recordings/Updater/Broker/HostOSProductVersion.swift +50 -0
- package/src/native/Recordings/Updater/Broker/InstallJournal.swift +362 -0
- package/src/native/Recordings/Updater/Broker/InstallRecovery.swift +662 -0
- package/src/native/Recordings/Updater/Broker/MonotonicState.swift +456 -0
- package/src/native/Recordings/Updater/Broker/PeerIdentity.swift +274 -0
- package/src/native/Recordings/Updater/Broker/VerifierRunner.swift +81 -0
- package/src/native/Recordings/Updater/BrokerTests/ActivationRecoveryPolicyTests.swift +259 -0
- package/src/native/Recordings/Updater/BrokerTests/CanonicalReleaseOrderTests.swift +21 -0
- package/src/native/Recordings/Updater/Client/ClientMain.swift +119 -0
- package/src/native/Recordings/Updater/Protocol/BoundedProcess.swift +159 -0
- package/src/native/Recordings/Updater/Protocol/CandidateMetadataPolicy.swift +214 -0
- package/src/native/Recordings/Updater/Protocol/HostOSVersionPolicy.swift +55 -0
- package/src/native/Recordings/Updater/Protocol/MonotonicReleasePolicy.swift +152 -0
- package/src/native/Recordings/Updater/Protocol/ReleaseEnvelope.swift +229 -0
- package/src/native/Recordings/Updater/Protocol/UpdateProtocol.swift +119 -0
- package/src/native/Recordings/Updater/ProtocolTests/BoundedProcessRunnerTests.swift +60 -0
- package/src/native/Recordings/Updater/ProtocolTests/CandidateMetadataPolicyTests.swift +168 -0
- package/src/native/Recordings/Updater/ProtocolTests/HostOSVersionPolicyTests.swift +62 -0
- package/src/native/Recordings/Updater/ProtocolTests/MonotonicReleasePolicyTests.swift +172 -0
- package/src/native/Recordings/Updater/ProtocolTests/ReleaseEnvelopeValidationTests.swift +65 -0
- package/src/native/Recordings/Updater/Signer/SignerMain.swift +210 -0
- package/src/native/Recordings/Updater/VerifierLauncher/RecordingsVerifierLauncher.c +725 -0
- package/src/native/Recordings/Updater/VerifierLauncher/include/RecordingsVerifierLauncher.h +33 -0
- package/src/native/Recordings/build.sh +1973 -236
- package/src/native/Recordings/RecordingsLib/MenuBarPopover.swift +0 -380
|
@@ -1,380 +0,0 @@
|
|
|
1
|
-
import SwiftUI
|
|
2
|
-
@preconcurrency import KeyboardShortcuts
|
|
3
|
-
|
|
4
|
-
public struct MenuBarPopover: View {
|
|
5
|
-
@ObservedObject public var engine: RecordingEngine
|
|
6
|
-
@ObservedObject public var shortcuts: VoiceShortcuts
|
|
7
|
-
@ObservedObject public var projectStore: ProjectStore
|
|
8
|
-
@State private var copiedIndex: Int?
|
|
9
|
-
@State private var filterProjectId: String?
|
|
10
|
-
|
|
11
|
-
public init(engine: RecordingEngine, shortcuts: VoiceShortcuts, projectStore: ProjectStore) {
|
|
12
|
-
self.engine = engine
|
|
13
|
-
self.shortcuts = shortcuts
|
|
14
|
-
self.projectStore = projectStore
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
private var filteredTranscriptions: [TranscriptionResult] {
|
|
18
|
-
guard let filter = filterProjectId else {
|
|
19
|
-
return engine.recentTranscriptions
|
|
20
|
-
}
|
|
21
|
-
if filter == "__none__" {
|
|
22
|
-
return engine.recentTranscriptions.filter { $0.projectId == nil }
|
|
23
|
-
}
|
|
24
|
-
return engine.recentTranscriptions.filter { $0.projectId == filter }
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
public var body: some View {
|
|
28
|
-
VStack(spacing: 0) {
|
|
29
|
-
header
|
|
30
|
-
.padding(.horizontal, 16).padding(.top, 12).padding(.bottom, 8)
|
|
31
|
-
modePicker
|
|
32
|
-
.padding(.horizontal, 16).padding(.top, 2)
|
|
33
|
-
recordingArea
|
|
34
|
-
.padding(.horizontal, 16).padding(.top, 10).padding(.bottom, 12)
|
|
35
|
-
Divider()
|
|
36
|
-
if !engine.recentTranscriptions.isEmpty {
|
|
37
|
-
filterBar
|
|
38
|
-
.padding(.horizontal, 16).padding(.vertical, 6)
|
|
39
|
-
Divider()
|
|
40
|
-
}
|
|
41
|
-
recentList
|
|
42
|
-
.frame(maxHeight: 200)
|
|
43
|
-
Divider()
|
|
44
|
-
footerMenu
|
|
45
|
-
}
|
|
46
|
-
.animation(.smooth(duration: 0.25), value: engine.isRecording)
|
|
47
|
-
.animation(.smooth(duration: 0.25), value: engine.isTranscribing)
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
// MARK: - Header
|
|
51
|
-
|
|
52
|
-
private var header: some View {
|
|
53
|
-
VStack(alignment: .leading, spacing: 4) {
|
|
54
|
-
HStack {
|
|
55
|
-
HasnaLogoMark(size: 22)
|
|
56
|
-
Text("Hasna Recordings")
|
|
57
|
-
.font(.headline)
|
|
58
|
-
Spacer()
|
|
59
|
-
if let shortcut = KeyboardShortcuts.getShortcut(for: .toggleRecording) {
|
|
60
|
-
Text(shortcut.description)
|
|
61
|
-
.font(.caption)
|
|
62
|
-
.foregroundStyle(.secondary)
|
|
63
|
-
.padding(.horizontal, 7)
|
|
64
|
-
.padding(.vertical, 3)
|
|
65
|
-
.glassEffect(.regular, in: .capsule)
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
if !projectStore.settings.projects.isEmpty {
|
|
69
|
-
HStack(spacing: 4) {
|
|
70
|
-
Image(systemName: "folder.fill")
|
|
71
|
-
.foregroundStyle(projectStore.activeProject != nil ? AnyShapeStyle(.tint) : AnyShapeStyle(.secondary))
|
|
72
|
-
Menu {
|
|
73
|
-
Button("None") { projectStore.setActive(nil) }
|
|
74
|
-
Divider()
|
|
75
|
-
ForEach(projectStore.settings.projects) { project in
|
|
76
|
-
Button {
|
|
77
|
-
projectStore.setActive(project.id)
|
|
78
|
-
} label: {
|
|
79
|
-
HStack {
|
|
80
|
-
Text(project.name)
|
|
81
|
-
if project.id == projectStore.settings.activeProjectId {
|
|
82
|
-
Image(systemName: "checkmark")
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
} label: {
|
|
88
|
-
Text(projectStore.activeProject?.name ?? "No project")
|
|
89
|
-
.foregroundStyle(projectStore.activeProject == nil ? .secondary : .primary)
|
|
90
|
-
}
|
|
91
|
-
.menuStyle(.borderlessButton)
|
|
92
|
-
.fixedSize()
|
|
93
|
-
Spacer()
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
// MARK: - Recording Area
|
|
100
|
-
|
|
101
|
-
private var modePicker: some View {
|
|
102
|
-
Picker("Mode", selection: $engine.mode) {
|
|
103
|
-
ForEach(RecordingMode.allCases) { mode in
|
|
104
|
-
Image(systemName: mode.icon)
|
|
105
|
-
.tag(mode)
|
|
106
|
-
.help(mode.rawValue)
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
.pickerStyle(.segmented)
|
|
110
|
-
.help(engine.mode.hint)
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
@ViewBuilder
|
|
114
|
-
private var recordingArea: some View {
|
|
115
|
-
if engine.isRecording {
|
|
116
|
-
VStack(spacing: 8) {
|
|
117
|
-
HStack {
|
|
118
|
-
Image(systemName: "waveform")
|
|
119
|
-
.symbolEffect(.variableColor.iterative, isActive: true)
|
|
120
|
-
.foregroundStyle(.red)
|
|
121
|
-
Text(fmt(engine.recordingDuration))
|
|
122
|
-
.monospacedDigit()
|
|
123
|
-
.contentTransition(.numericText())
|
|
124
|
-
.font(.title3.weight(.medium))
|
|
125
|
-
Spacer()
|
|
126
|
-
Button("Stop", systemImage: "stop.fill") { engine.stopAndTranscribe() }
|
|
127
|
-
.buttonStyle(.glassProminent)
|
|
128
|
-
.tint(.red)
|
|
129
|
-
.controlSize(.small)
|
|
130
|
-
}
|
|
131
|
-
if !engine.liveTranscriptionText.isEmpty {
|
|
132
|
-
Text(engine.liveTranscriptionText)
|
|
133
|
-
.font(.callout)
|
|
134
|
-
.foregroundStyle(.primary)
|
|
135
|
-
.frame(maxWidth: .infinity, alignment: .leading)
|
|
136
|
-
.lineLimit(3)
|
|
137
|
-
.contentTransition(.opacity)
|
|
138
|
-
} else {
|
|
139
|
-
Text("Listening…")
|
|
140
|
-
.font(.caption)
|
|
141
|
-
.foregroundStyle(.secondary)
|
|
142
|
-
.frame(maxWidth: .infinity, alignment: .leading)
|
|
143
|
-
}
|
|
144
|
-
if let project = projectStore.activeProject {
|
|
145
|
-
activeProjectTag(project)
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
.padding(12)
|
|
149
|
-
.glassEffect(.regular.tint(.red.opacity(0.1)), in: .rect(cornerRadius: 14))
|
|
150
|
-
} else if engine.isTranscribing {
|
|
151
|
-
VStack(spacing: 8) {
|
|
152
|
-
HStack(spacing: 8) {
|
|
153
|
-
ProgressView().controlSize(.small)
|
|
154
|
-
Text("Transcribing…")
|
|
155
|
-
.foregroundStyle(.secondary)
|
|
156
|
-
Spacer()
|
|
157
|
-
}
|
|
158
|
-
if !engine.liveTranscriptionText.isEmpty {
|
|
159
|
-
Text(engine.liveTranscriptionText)
|
|
160
|
-
.font(.callout)
|
|
161
|
-
.foregroundStyle(.primary)
|
|
162
|
-
.frame(maxWidth: .infinity, alignment: .leading)
|
|
163
|
-
.lineLimit(3)
|
|
164
|
-
}
|
|
165
|
-
if let project = projectStore.activeProject {
|
|
166
|
-
activeProjectTag(project)
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
.frame(maxWidth: .infinity)
|
|
170
|
-
.padding(12)
|
|
171
|
-
.glassEffect(.regular, in: .rect(cornerRadius: 14))
|
|
172
|
-
} else {
|
|
173
|
-
VStack(spacing: 8) {
|
|
174
|
-
Button {
|
|
175
|
-
engine.startRecording()
|
|
176
|
-
} label: {
|
|
177
|
-
Label("Record", systemImage: "mic.fill")
|
|
178
|
-
.frame(maxWidth: .infinity)
|
|
179
|
-
}
|
|
180
|
-
.buttonStyle(.glassProminent)
|
|
181
|
-
.controlSize(.large)
|
|
182
|
-
|
|
183
|
-
if let shortcut = KeyboardShortcuts.getShortcut(for: .toggleRecording) {
|
|
184
|
-
Text("or hold \(shortcut.description)")
|
|
185
|
-
.font(.caption)
|
|
186
|
-
.foregroundStyle(.tertiary)
|
|
187
|
-
}
|
|
188
|
-
Text(engine.statusMessage)
|
|
189
|
-
.font(.caption)
|
|
190
|
-
.foregroundStyle(engine.statusMessage == "Ready" ? Color.secondary : Color.orange)
|
|
191
|
-
.multilineTextAlignment(.center)
|
|
192
|
-
.lineLimit(2)
|
|
193
|
-
}
|
|
194
|
-
.frame(maxWidth: .infinity)
|
|
195
|
-
.padding(12)
|
|
196
|
-
.glassEffect(.regular, in: .rect(cornerRadius: 14))
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
private func activeProjectTag(_ project: RecProject) -> some View {
|
|
201
|
-
HStack(spacing: 4) {
|
|
202
|
-
Image(systemName: "folder.fill").foregroundStyle(.tint)
|
|
203
|
-
Text(project.name).foregroundStyle(.secondary)
|
|
204
|
-
Spacer()
|
|
205
|
-
}
|
|
206
|
-
.font(.caption)
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
// MARK: - Filter Bar
|
|
210
|
-
|
|
211
|
-
private var filterBar: some View {
|
|
212
|
-
ScrollView(.horizontal, showsIndicators: false) {
|
|
213
|
-
HStack(spacing: 6) {
|
|
214
|
-
FilterChip(label: "All", isActive: filterProjectId == nil) {
|
|
215
|
-
filterProjectId = nil
|
|
216
|
-
}
|
|
217
|
-
ForEach(projectStore.settings.projects) { project in
|
|
218
|
-
let count = engine.recentTranscriptions.filter { $0.projectId == project.id }.count
|
|
219
|
-
if count > 0 {
|
|
220
|
-
FilterChip(
|
|
221
|
-
label: "\(project.name) (\(count))",
|
|
222
|
-
isActive: filterProjectId == project.id
|
|
223
|
-
) {
|
|
224
|
-
filterProjectId = filterProjectId == project.id ? nil : project.id
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
let noProjectCount = engine.recentTranscriptions.filter { $0.projectId == nil }.count
|
|
229
|
-
if noProjectCount > 0 {
|
|
230
|
-
FilterChip(
|
|
231
|
-
label: "No project (\(noProjectCount))",
|
|
232
|
-
isActive: filterProjectId == "__none__"
|
|
233
|
-
) {
|
|
234
|
-
filterProjectId = filterProjectId == "__none__" ? nil : "__none__"
|
|
235
|
-
}
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
// MARK: - Recent
|
|
242
|
-
|
|
243
|
-
private var recentList: some View {
|
|
244
|
-
Group {
|
|
245
|
-
if filteredTranscriptions.isEmpty {
|
|
246
|
-
Spacer()
|
|
247
|
-
} else {
|
|
248
|
-
ScrollView {
|
|
249
|
-
LazyVStack(alignment: .leading, spacing: 2) {
|
|
250
|
-
ForEach(filteredTranscriptions.indices, id: \.self) { i in
|
|
251
|
-
let item = filteredTranscriptions[i]
|
|
252
|
-
TranscriptionRow(
|
|
253
|
-
item: item,
|
|
254
|
-
showProject: filterProjectId == nil,
|
|
255
|
-
isCopied: copiedIndex == i
|
|
256
|
-
) {
|
|
257
|
-
withAnimation(.easeInOut(duration: 0.15)) {
|
|
258
|
-
copiedIndex = i
|
|
259
|
-
}
|
|
260
|
-
engine.pasteIntoFrontApp(item.displayText)
|
|
261
|
-
DispatchQueue.main.asyncAfter(deadline: .now() + 1.2) {
|
|
262
|
-
withAnimation { if copiedIndex == i { copiedIndex = nil } }
|
|
263
|
-
}
|
|
264
|
-
}
|
|
265
|
-
}
|
|
266
|
-
}
|
|
267
|
-
.padding(.horizontal, 16).padding(.vertical, 6)
|
|
268
|
-
}
|
|
269
|
-
}
|
|
270
|
-
}
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
// MARK: - Footer
|
|
274
|
-
|
|
275
|
-
private var footerMenu: some View {
|
|
276
|
-
VStack(spacing: 0) {
|
|
277
|
-
SettingsLink {
|
|
278
|
-
HStack {
|
|
279
|
-
Text("Settings...")
|
|
280
|
-
Spacer()
|
|
281
|
-
Text("⌘,").foregroundStyle(.tertiary)
|
|
282
|
-
}
|
|
283
|
-
.contentShape(Rectangle())
|
|
284
|
-
.padding(.horizontal, 16).padding(.vertical, 6)
|
|
285
|
-
}
|
|
286
|
-
.buttonStyle(.plain)
|
|
287
|
-
|
|
288
|
-
Divider()
|
|
289
|
-
|
|
290
|
-
Button {
|
|
291
|
-
NSApplication.shared.terminate(nil)
|
|
292
|
-
} label: {
|
|
293
|
-
HStack {
|
|
294
|
-
Text("Quit Hasna Recordings")
|
|
295
|
-
Spacer()
|
|
296
|
-
Text("⌘Q").foregroundStyle(.tertiary)
|
|
297
|
-
}
|
|
298
|
-
.contentShape(Rectangle())
|
|
299
|
-
.padding(.horizontal, 16).padding(.vertical, 6)
|
|
300
|
-
}
|
|
301
|
-
.buttonStyle(.plain)
|
|
302
|
-
}
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
private func fmt(_ t: TimeInterval) -> String {
|
|
306
|
-
String(format: "%d:%02d", Int(t) / 60, Int(t) % 60)
|
|
307
|
-
}
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
// MARK: - Filter Chip
|
|
311
|
-
|
|
312
|
-
struct FilterChip: View {
|
|
313
|
-
let label: String
|
|
314
|
-
let isActive: Bool
|
|
315
|
-
let action: () -> Void
|
|
316
|
-
|
|
317
|
-
var body: some View {
|
|
318
|
-
Button(action: action) {
|
|
319
|
-
Text(label)
|
|
320
|
-
.font(.caption)
|
|
321
|
-
.padding(.horizontal, 9)
|
|
322
|
-
.padding(.vertical, 4)
|
|
323
|
-
}
|
|
324
|
-
.buttonStyle(.plain)
|
|
325
|
-
.foregroundStyle(isActive ? .primary : .secondary)
|
|
326
|
-
.glassEffect(
|
|
327
|
-
isActive ? .regular.tint(.accentColor.opacity(0.25)).interactive() : .regular.interactive(),
|
|
328
|
-
in: .capsule
|
|
329
|
-
)
|
|
330
|
-
}
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
// MARK: - Transcription Row
|
|
334
|
-
|
|
335
|
-
struct TranscriptionRow: View {
|
|
336
|
-
let item: TranscriptionResult
|
|
337
|
-
let showProject: Bool
|
|
338
|
-
let isCopied: Bool
|
|
339
|
-
let onCopy: () -> Void
|
|
340
|
-
|
|
341
|
-
var body: some View {
|
|
342
|
-
VStack(alignment: .leading, spacing: 2) {
|
|
343
|
-
HStack(alignment: .top, spacing: 8) {
|
|
344
|
-
Text(item.displayText)
|
|
345
|
-
.lineLimit(2)
|
|
346
|
-
.frame(maxWidth: .infinity, alignment: .leading)
|
|
347
|
-
|
|
348
|
-
if isCopied {
|
|
349
|
-
Image(systemName: "checkmark")
|
|
350
|
-
.foregroundStyle(.green)
|
|
351
|
-
.transition(.scale.combined(with: .opacity))
|
|
352
|
-
} else {
|
|
353
|
-
Text(relativeTime(item.timestamp))
|
|
354
|
-
.foregroundStyle(.tertiary)
|
|
355
|
-
}
|
|
356
|
-
}
|
|
357
|
-
if showProject, let name = item.projectName {
|
|
358
|
-
HStack(spacing: 3) {
|
|
359
|
-
Image(systemName: "folder")
|
|
360
|
-
Text(name)
|
|
361
|
-
}
|
|
362
|
-
.foregroundStyle(.tertiary)
|
|
363
|
-
}
|
|
364
|
-
}
|
|
365
|
-
.padding(.vertical, 4)
|
|
366
|
-
.padding(.horizontal, 6)
|
|
367
|
-
.contentShape(Rectangle())
|
|
368
|
-
.onTapGesture { onCopy() }
|
|
369
|
-
.onHover { hovering in
|
|
370
|
-
if hovering { NSCursor.pointingHand.push() } else { NSCursor.pop() }
|
|
371
|
-
}
|
|
372
|
-
}
|
|
373
|
-
|
|
374
|
-
private func relativeTime(_ date: Date) -> String {
|
|
375
|
-
let s = Date().timeIntervalSince(date)
|
|
376
|
-
if s < 60 { return "now" }
|
|
377
|
-
if s < 3600 { return "\(Int(s / 60))m" }
|
|
378
|
-
return "\(Int(s / 3600))h"
|
|
379
|
-
}
|
|
380
|
-
}
|