@@ -19,6 +19,9 @@ final class EventStream: ObservableObject {
1919 private var task : URLSessionWebSocketTask ?
2020 private var reconnectWorkItem : DispatchWorkItem ?
2121
22+ // Pending transcription continuations keyed by request ID
23+ private var transcriptContinuations : [ String : CheckedContinuation < String , Error > ] = [ : ]
24+
2225 private init ( ) { }
2326
2427 // MARK: - Connection
@@ -62,16 +65,78 @@ final class EventStream: ObservableObject {
6265 DispatchQueue . main. asyncAfter ( deadline: . now( ) + 2 , execute: item)
6366 }
6467
68+ // MARK: - Streaming transcription
69+
70+ @Published var partialTranscript : String = " "
71+ private var activeSessionID : String ?
72+
73+ func startTranscription( bundleID: String ? ) {
74+ let sessionID = UUID ( ) . uuidString
75+ activeSessionID = sessionID
76+ partialTranscript = " "
77+ sendMessage ( [
78+ " type " : " start_transcription " ,
79+ " id " : sessionID,
80+ " bundleID " : bundleID as Any ,
81+ ] )
82+ }
83+
84+ func sendAudioChunk( _ pcm: Data ) {
85+ guard let sessionID = activeSessionID else { return }
86+ sendMessage ( [
87+ " type " : " audio_chunk " ,
88+ " id " : sessionID,
89+ " data " : pcm. base64EncodedString ( ) ,
90+ ] )
91+ }
92+
93+ func stopTranscription( ) async throws -> String {
94+ guard let sessionID = activeSessionID else {
95+ throw URLError ( . unknown)
96+ }
97+ activeSessionID = nil
98+ sendMessage ( [ " type " : " stop_transcription " , " id " : sessionID] )
99+ return try await withCheckedThrowingContinuation { continuation in
100+ transcriptContinuations [ sessionID] = continuation
101+ }
102+ }
103+
104+ private func sendMessage( _ payload: [ String : Any ] ) {
105+ guard let data = try ? JSONSerialization . data ( withJSONObject: payload) ,
106+ let text = String ( data: data, encoding: . utf8) else { return }
107+ task? . send ( . string( text) ) { _ in }
108+ }
109+
65110 // MARK: - Event dispatch
66111
67112 private func dispatch( _ text: String ) {
68113 guard
69114 let data = text. data ( using: . utf8) ,
70115 let json = try ? JSONSerialization . jsonObject ( with: data) as? [ String : Any ] ,
71- let event = json [ " event " ] as? String ,
72- let payload = json [ " payload " ]
116+ let event = json [ " event " ] as? String
73117 else { return }
74118
119+ // Partial transcript — update live display
120+ if event == " partial_transcript " ,
121+ let payload = json [ " payload " ] as? [ String : Any ] ,
122+ let text = payload [ " text " ] as? String {
123+ DispatchQueue . main. async { self . partialTranscript = text }
124+ return
125+ }
126+
127+ // Final transcript — resume the waiting continuation
128+ if event == " transcript " ,
129+ let reqID = json [ " id " ] as? String ,
130+ let payload = json [ " payload " ] as? [ String : Any ] ,
131+ let transcript = payload [ " transcript " ] as? String {
132+ DispatchQueue . main. async { self . partialTranscript = " " }
133+ let continuation = transcriptContinuations. removeValue ( forKey: reqID)
134+ continuation? . resume ( returning: transcript)
135+ return
136+ }
137+
138+ guard let payload = json [ " payload " ] else { return }
139+
75140 DispatchQueue . main. async {
76141 switch event {
77142 case " agent-status " :
0 commit comments