Skip to content

Commit 6657fb7

Browse files
author
Atharva Vaidya
committed
feat: parsing JSON chunks
1 parent 12b5099 commit 6657fb7

File tree

3 files changed

+72
-40
lines changed

3 files changed

+72
-40
lines changed
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
//
2+
// File.swift
3+
// CompilerSwiftAI
4+
//
5+
// Created by Atharva Vaidya on 2/23/25.
6+
//
7+
8+
import Foundation
9+
10+
struct ChatResponseDTO: Decodable {
11+
let content: String
12+
}

Sources/CompilerSwiftAI/Model Calling/CompilerClient+Streaming.swift

Lines changed: 41 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -85,32 +85,16 @@ extension CompilerClient {
8585
}
8686

8787
for try await line in asyncBytes.lines {
88-
modelLogger.debug("Raw SSE line [\(line.count) bytes]: \(line)")
88+
modelLogger.debug("Raw SSE line: \(line)")
8989

90-
// Skip non-SSE lines (like id: lines)
91-
guard line.hasPrefix("data:") else {
92-
modelLogger.debug("Skipping non-SSE line")
90+
guard let content = try parseChatResponse(from: line) else {
9391
continue
9492
}
9593

96-
// Get everything after "data:"
97-
let content = String(line.dropFirst("data:".count))
98-
99-
// If it's just a space or empty after "data:", yield a newline
100-
if content.trimmingCharacters(in: .whitespaces).isEmpty {
101-
modelLogger.debug("Empty data line - yielding newline")
102-
continuation.yield("\n")
103-
continue
104-
}
105-
106-
// For non-empty content, trim just the leading space after "data:"
107-
let trimmedContent = content.hasPrefix(" ") ? String(content.dropFirst()) : content
108-
modelLogger.debug("Content: \(trimmedContent.debugDescription)")
109-
modelLogger.debug("Yielding content of length: \(trimmedContent.count)")
110-
111-
// Yield the content directly - server handles JSON extraction
112-
continuation.yield(trimmedContent)
94+
modelLogger.debug("Content: \(content.debugDescription)")
11395

96+
continuation.yield(content)
97+
11498
modelLogger.debug("Content yielded successfully")
11599
}
116100

@@ -175,4 +159,40 @@ extension CompilerClient {
175159
}
176160
}
177161
}
162+
163+
private func parseChatResponse(from line: String) throws -> String? {
164+
// Skip empty lines and comments
165+
guard !line.isEmpty, !line.hasPrefix(":") else {
166+
return nil
167+
}
168+
169+
// Extract the data part from the SSE format
170+
guard line.hasPrefix("data: ") else {
171+
return nil
172+
}
173+
174+
let jsonString = String(line.dropFirst(6))
175+
176+
guard let parsedResponse = try? parseEventMessage(from: jsonString) else {
177+
print("Couldn't parse repsonse")
178+
return nil
179+
}
180+
181+
return parsedResponse.content
182+
}
183+
184+
private func parseEventMessage(from line: String) throws -> ChatResponseDTO? {
185+
guard let data = line.data(using: .utf8) else {
186+
print("[ChatStreamer] ❌ Failed to convert string to data: \(line)")
187+
return nil
188+
}
189+
190+
do {
191+
let message = try JSONDecoder().decode(ChatResponseDTO.self, from: data)
192+
return message
193+
} catch {
194+
print("[ChatStreamer] ❌ JSON decode error: \(error)")
195+
return nil
196+
}
197+
}
178198
}

Sources/CompilerSwiftAI/UI/Chat/ChatView/ChatViewModel.swift

Lines changed: 19 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,7 @@ class ChatViewModel: Transcribable {
130130
private func observeMessageStream() async {
131131
let throttleInterval: TimeInterval = 0.15
132132
var lastUpdateTime = Date.distantPast
133-
var lastMessages: [Message] = []
133+
// var lastMessages: [Message] = []
134134

135135
logger.log("observeMessageStream started. Now waiting for new messages...")
136136

@@ -143,33 +143,33 @@ class ChatViewModel: Transcribable {
143143
logger.log("Received newMessages from actor, count = \(newMessages.count). Checking diff...")
144144

145145
// 1) Check if the array is truly different from what we last published
146-
guard newMessages != lastMessages else {
147-
logger.log("No diff from lastMessages (count=\(lastMessages.count)). Skipping update to avoid spam.")
148-
continue
149-
}
146+
// guard newMessages != lastMessages else {
147+
// logger.log("No diff from lastMessages (count=\(lastMessages.count)). Skipping update to avoid spam.")
148+
// continue
149+
// }
150150

151151
// 2) If we have *too many updates in quick succession*, we do a small throttle
152-
let now = Date()
153-
let elapsed = now.timeIntervalSince(lastUpdateTime)
154-
let needed = throttleInterval - elapsed
155-
if needed > 0 {
156-
logger.log("Throttling. Sleeping for \(String(format: "%.2f", needed))s")
157-
try? await Task.sleep(nanoseconds: UInt64(needed * 1_000_000_000))
158-
159-
// Check again after sleep if task was cancelled
160-
guard !Task.isCancelled else { break }
161-
}
162-
152+
// let now = Date()
153+
// let elapsed = now.timeIntervalSince(lastUpdateTime)
154+
// let needed = throttleInterval - elapsed
155+
// if needed > 0 {
156+
// logger.log("Throttling. Sleeping for \(String(format: "%.2f", needed))s")
157+
// try? await Task.sleep(nanoseconds: UInt64(needed * 1_000_000_000))
158+
//
159+
// // Check again after sleep if task was cancelled
160+
// guard !Task.isCancelled else { break }
161+
// }
162+
//
163163
// 3) Now actually publish these messages to SwiftUI
164164
logger.log("Publishing updated messages to SwiftUI. count=\(newMessages.count)")
165165
await MainActor.run {
166166
// Only update if the messages are still different
167-
guard self.messages != newMessages else { return }
167+
// guard self.messages != newMessages else { return }
168168
self.messages = newMessages
169169
}
170170

171-
lastMessages = newMessages
172-
lastUpdateTime = now
171+
// lastMessages = newMessages
172+
// lastUpdateTime = now
173173
}
174174

175175
logger.log("observeMessageStream completed or was cancelled.")

0 commit comments

Comments
 (0)