@hasna/recordings 0.1.16 → 0.1.17
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/dist/cli/index.js +2 -2
- package/dist/mcp/index.js +2 -2
- package/dist/version.d.ts +1 -1
- package/package.json +1 -1
- package/src/native/Recordings/RecordingsLib/RealtimeTranscriptionClient.swift +6 -47
- package/src/native/Recordings/RecordingsLib/RecordingEngine.swift +6 -17
- package/src/native/Recordings/RecordingsTests/RealtimeTranscriptionTests.swift +2 -18
package/dist/cli/index.js
CHANGED
|
@@ -7,7 +7,7 @@ var __require = import.meta.require;
|
|
|
7
7
|
var require_package = __commonJS((exports, module) => {
|
|
8
8
|
module.exports = {
|
|
9
9
|
name: "@hasna/recordings",
|
|
10
|
-
version: "0.1.
|
|
10
|
+
version: "0.1.17",
|
|
11
11
|
type: "module",
|
|
12
12
|
description: "Speech-to-text recording tool with MCP and CLI \u2014 records, transcribes, and optionally enhances text using AI",
|
|
13
13
|
repository: {
|
|
@@ -10466,7 +10466,7 @@ async function processText(rawText, config, systemPrompt) {
|
|
|
10466
10466
|
}
|
|
10467
10467
|
|
|
10468
10468
|
// src/version.ts
|
|
10469
|
-
var VERSION = "0.1.
|
|
10469
|
+
var VERSION = "0.1.17";
|
|
10470
10470
|
|
|
10471
10471
|
// src/cli/index.ts
|
|
10472
10472
|
var program = new Command;
|
package/dist/mcp/index.js
CHANGED
|
@@ -21,7 +21,7 @@ var __require = import.meta.require;
|
|
|
21
21
|
var require_package = __commonJS((exports, module) => {
|
|
22
22
|
module.exports = {
|
|
23
23
|
name: "@hasna/recordings",
|
|
24
|
-
version: "0.1.
|
|
24
|
+
version: "0.1.17",
|
|
25
25
|
type: "module",
|
|
26
26
|
description: "Speech-to-text recording tool with MCP and CLI \u2014 records, transcribes, and optionally enhances text using AI",
|
|
27
27
|
repository: {
|
|
@@ -14983,7 +14983,7 @@ async function processText(rawText, config, systemPrompt) {
|
|
|
14983
14983
|
}
|
|
14984
14984
|
|
|
14985
14985
|
// src/version.ts
|
|
14986
|
-
var VERSION = "0.1.
|
|
14986
|
+
var VERSION = "0.1.17";
|
|
14987
14987
|
|
|
14988
14988
|
// src/mcp/index.ts
|
|
14989
14989
|
var config = loadConfig();
|
package/dist/version.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare const VERSION = "0.1.
|
|
1
|
+
export declare const VERSION = "0.1.17";
|
|
2
2
|
//# sourceMappingURL=version.d.ts.map
|
package/package.json
CHANGED
|
@@ -17,7 +17,6 @@ public final class RealtimeTranscriptionClient: ObservableObject, @unchecked Sen
|
|
|
17
17
|
private var ws: URLSessionWebSocketTask?
|
|
18
18
|
private var receiveTask: Task<Void, Never>?
|
|
19
19
|
private var outboundEventTask: Task<Void, Never>?
|
|
20
|
-
private var liveCommitTask: Task<Void, Never>?
|
|
21
20
|
private var isConfigured = false
|
|
22
21
|
private var pendingAudioChunks: [Data] = []
|
|
23
22
|
private var itemOrder: [String] = []
|
|
@@ -29,11 +28,8 @@ public final class RealtimeTranscriptionClient: ObservableObject, @unchecked Sen
|
|
|
29
28
|
private var queuedCommitCount = 0
|
|
30
29
|
private var uncommittedAudioBytes = 0
|
|
31
30
|
private var lastRealtimeEventAt = Date.distantPast
|
|
32
|
-
private var lastCommitQueuedAt = Date.distantPast
|
|
33
31
|
|
|
34
32
|
private nonisolated static let minimumManualCommitBytes = 5_760
|
|
35
|
-
private nonisolated static let minimumLiveCommitBytes = 38_400
|
|
36
|
-
private nonisolated static let liveCommitIntervalSeconds: TimeInterval = 0.8
|
|
37
33
|
|
|
38
34
|
private let apiKey: String
|
|
39
35
|
private let homePath: String
|
|
@@ -70,7 +66,6 @@ public final class RealtimeTranscriptionClient: ObservableObject, @unchecked Sen
|
|
|
70
66
|
queuedCommitCount = 0
|
|
71
67
|
uncommittedAudioBytes = 0
|
|
72
68
|
lastRealtimeEventAt = Date()
|
|
73
|
-
lastCommitQueuedAt = Date()
|
|
74
69
|
error = nil
|
|
75
70
|
|
|
76
71
|
var request = URLRequest(url: Self.transcriptionURL)
|
|
@@ -122,7 +117,6 @@ public final class RealtimeTranscriptionClient: ObservableObject, @unchecked Sen
|
|
|
122
117
|
"audio": base64,
|
|
123
118
|
]
|
|
124
119
|
enqueueOutboundEvent(msg)
|
|
125
|
-
scheduleLiveCommitIfNeeded()
|
|
126
120
|
}
|
|
127
121
|
|
|
128
122
|
/// Signal end of input — triggers final transcription completion.
|
|
@@ -140,7 +134,6 @@ public final class RealtimeTranscriptionClient: ObservableObject, @unchecked Sen
|
|
|
140
134
|
uncommittedAudioBytes = 0
|
|
141
135
|
queuedCommitCount += 1
|
|
142
136
|
lastRealtimeEventAt = Date()
|
|
143
|
-
lastCommitQueuedAt = Date()
|
|
144
137
|
NativeAppLog.write("realtime commit queued reason=\(reason) bytes=\(bytesToCommit)", homePath: homePath)
|
|
145
138
|
let commitTask = enqueueOutboundEvent(msg)
|
|
146
139
|
await commitTask?.value
|
|
@@ -178,11 +171,9 @@ public final class RealtimeTranscriptionClient: ObservableObject, @unchecked Sen
|
|
|
178
171
|
receiveTask?.cancel()
|
|
179
172
|
ws?.cancel(with: .normalClosure, reason: nil)
|
|
180
173
|
outboundEventTask?.cancel()
|
|
181
|
-
liveCommitTask?.cancel()
|
|
182
174
|
ws = nil
|
|
183
175
|
receiveTask = nil
|
|
184
176
|
outboundEventTask = nil
|
|
185
|
-
liveCommitTask = nil
|
|
186
177
|
pendingAudioChunks.removeAll(keepingCapacity: true)
|
|
187
178
|
let text = accumulatedText
|
|
188
179
|
return text
|
|
@@ -285,25 +276,6 @@ public final class RealtimeTranscriptionClient: ObservableObject, @unchecked Sen
|
|
|
285
276
|
return task
|
|
286
277
|
}
|
|
287
278
|
|
|
288
|
-
private func scheduleLiveCommitIfNeeded(now: Date = Date()) {
|
|
289
|
-
guard liveCommitTask == nil else { return }
|
|
290
|
-
let secondsSinceLastCommit = now.timeIntervalSince(lastCommitQueuedAt)
|
|
291
|
-
guard Self.shouldAutoCommitLiveInput(
|
|
292
|
-
uncommittedAudioBytes: uncommittedAudioBytes,
|
|
293
|
-
secondsSinceLastCommit: secondsSinceLastCommit
|
|
294
|
-
) else { return }
|
|
295
|
-
|
|
296
|
-
liveCommitTask = Task { [weak self] in
|
|
297
|
-
_ = await self?.commitLiveInput()
|
|
298
|
-
}
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
@discardableResult
|
|
302
|
-
private func commitLiveInput() async -> Bool {
|
|
303
|
-
defer { liveCommitTask = nil }
|
|
304
|
-
return await commitInput(reason: "live")
|
|
305
|
-
}
|
|
306
|
-
|
|
307
279
|
private func registerItem(_ itemID: String, previousItemID: String?) {
|
|
308
280
|
guard !itemOrder.contains(itemID) else { return }
|
|
309
281
|
if let previousItemID, let previousIndex = itemOrder.firstIndex(of: previousItemID) {
|
|
@@ -383,14 +355,6 @@ public final class RealtimeTranscriptionClient: ObservableObject, @unchecked Sen
|
|
|
383
355
|
uncommittedAudioBytes >= minimumManualCommitBytes
|
|
384
356
|
}
|
|
385
357
|
|
|
386
|
-
private nonisolated static func shouldAutoCommitLiveInput(
|
|
387
|
-
uncommittedAudioBytes: Int,
|
|
388
|
-
secondsSinceLastCommit: TimeInterval
|
|
389
|
-
) -> Bool {
|
|
390
|
-
uncommittedAudioBytes >= minimumLiveCommitBytes &&
|
|
391
|
-
secondsSinceLastCommit >= liveCommitIntervalSeconds
|
|
392
|
-
}
|
|
393
|
-
|
|
394
358
|
private nonisolated static func transcriptionSessionUpdateEvent(transcription: [String: Any]) -> [String: Any] {
|
|
395
359
|
[
|
|
396
360
|
"type": "session.update",
|
|
@@ -403,7 +367,12 @@ public final class RealtimeTranscriptionClient: ObservableObject, @unchecked Sen
|
|
|
403
367
|
"rate": 24_000,
|
|
404
368
|
],
|
|
405
369
|
"transcription": transcription,
|
|
406
|
-
"turn_detection":
|
|
370
|
+
"turn_detection": [
|
|
371
|
+
"type": "server_vad",
|
|
372
|
+
"threshold": 0.5,
|
|
373
|
+
"prefix_padding_ms": 300,
|
|
374
|
+
"silence_duration_ms": 350,
|
|
375
|
+
],
|
|
407
376
|
"noise_reduction": [
|
|
408
377
|
"type": "near_field",
|
|
409
378
|
],
|
|
@@ -458,16 +427,6 @@ extension RealtimeTranscriptionClient {
|
|
|
458
427
|
shouldManuallyCommit(uncommittedAudioBytes: uncommittedAudioBytes)
|
|
459
428
|
}
|
|
460
429
|
|
|
461
|
-
public nonisolated static func shouldAutoCommitLiveInputTestHelper(
|
|
462
|
-
uncommittedAudioBytes: Int,
|
|
463
|
-
secondsSinceLastCommit: TimeInterval
|
|
464
|
-
) -> Bool {
|
|
465
|
-
shouldAutoCommitLiveInput(
|
|
466
|
-
uncommittedAudioBytes: uncommittedAudioBytes,
|
|
467
|
-
secondsSinceLastCommit: secondsSinceLastCommit
|
|
468
|
-
)
|
|
469
|
-
}
|
|
470
|
-
|
|
471
430
|
public nonisolated static func sessionUpdateTestHelper(prompt: String, language: String = "") -> [String: Any] {
|
|
472
431
|
var transcription: [String: Any] = [
|
|
473
432
|
"model": modelID,
|
|
@@ -382,13 +382,12 @@ public final class RecordingEngine: ObservableObject {
|
|
|
382
382
|
// MARK: - Real-time Streaming
|
|
383
383
|
|
|
384
384
|
private func startRealtimeStreaming(apiKey: String) {
|
|
385
|
-
let systemPrompt = projectStore?.effectiveSystemPrompt ?? ""
|
|
386
385
|
let client = RealtimeTranscriptionClient(apiKey: apiKey, homePath: home)
|
|
387
386
|
realtimeClient = client
|
|
388
387
|
log("realtime streaming task starting")
|
|
389
388
|
|
|
390
389
|
streamingTask = Task {
|
|
391
|
-
await client.startStreaming(
|
|
390
|
+
await client.startStreaming()
|
|
392
391
|
self.log("realtime start completed streaming=\(client.isStreaming) error=\(client.error ?? "")")
|
|
393
392
|
|
|
394
393
|
// Receive deltas
|
|
@@ -453,25 +452,15 @@ public final class RecordingEngine: ObservableObject {
|
|
|
453
452
|
self.streamingTask?.cancel()
|
|
454
453
|
self.streamingTask = nil
|
|
455
454
|
|
|
456
|
-
let
|
|
455
|
+
let realtimeText = streamingResult.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty ? nil : streamingResult
|
|
457
456
|
|
|
458
457
|
self.liveTranscriptionText = ""
|
|
459
458
|
|
|
460
|
-
if let
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
self.finishWithText(
|
|
464
|
-
text,
|
|
465
|
-
curMode: curMode,
|
|
466
|
-
targetAppBundleIdentifier: targetAppBundleIdentifier,
|
|
467
|
-
activeProjectId: activeProjectId,
|
|
468
|
-
activeProjectName: activeProjectName
|
|
469
|
-
)
|
|
470
|
-
} else if let audioPath, self.writeCapturedWAV(to: audioPath) {
|
|
471
|
-
if let text {
|
|
472
|
-
self.log("realtime text looked partial chars=\(text.count); falling back to CLI")
|
|
459
|
+
if let audioPath, self.writeCapturedWAV(to: audioPath) {
|
|
460
|
+
if let realtimeText {
|
|
461
|
+
self.log("realtime preview ignored for final chars=\(realtimeText.count)")
|
|
473
462
|
}
|
|
474
|
-
self.log("
|
|
463
|
+
self.log("transcribing captured full audio audioPath=\(audioPath)")
|
|
475
464
|
self.fallbackTranscribe(
|
|
476
465
|
audioPath: audioPath,
|
|
477
466
|
curMode: curMode,
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import Foundation
|
|
2
1
|
import Testing
|
|
3
2
|
@testable import RecordingsLib
|
|
4
3
|
|
|
@@ -99,7 +98,8 @@ struct RealtimeTranscriptionTests {
|
|
|
99
98
|
#expect(transcription?["prompt"] as? String == "Use Alumia as vocabulary")
|
|
100
99
|
#expect(transcription?["language"] as? String == "en")
|
|
101
100
|
|
|
102
|
-
|
|
101
|
+
let turnDetection = input?["turn_detection"] as? [String: Any]
|
|
102
|
+
#expect(turnDetection?["type"] as? String == "server_vad")
|
|
103
103
|
|
|
104
104
|
let include = session?["include"] as? [String]
|
|
105
105
|
#expect(include?.contains("item.input_audio_transcription.logprobs") == true)
|
|
@@ -117,22 +117,6 @@ struct RealtimeTranscriptionTests {
|
|
|
117
117
|
#expect(RealtimeTranscriptionClient.shouldManuallyCommitTestHelper(uncommittedAudioBytes: 5_760) == true)
|
|
118
118
|
}
|
|
119
119
|
|
|
120
|
-
@Test("Live commits wait for enough audio and elapsed time")
|
|
121
|
-
func liveCommitThreshold() {
|
|
122
|
-
#expect(RealtimeTranscriptionClient.shouldAutoCommitLiveInputTestHelper(
|
|
123
|
-
uncommittedAudioBytes: 38_399,
|
|
124
|
-
secondsSinceLastCommit: 1.0
|
|
125
|
-
) == false)
|
|
126
|
-
#expect(RealtimeTranscriptionClient.shouldAutoCommitLiveInputTestHelper(
|
|
127
|
-
uncommittedAudioBytes: 38_400,
|
|
128
|
-
secondsSinceLastCommit: 0.79
|
|
129
|
-
) == false)
|
|
130
|
-
#expect(RealtimeTranscriptionClient.shouldAutoCommitLiveInputTestHelper(
|
|
131
|
-
uncommittedAudioBytes: 38_400,
|
|
132
|
-
secondsSinceLastCommit: 0.8
|
|
133
|
-
) == true)
|
|
134
|
-
}
|
|
135
|
-
|
|
136
120
|
@Test("Partial realtime text falls back for longer recordings")
|
|
137
121
|
func partialRealtimeFallback() {
|
|
138
122
|
#expect(RecordingEngine.shouldFallbackFromPartialRealtime(text: "Hi", pcmByteCount: 96_000) == true)
|