feat: add Voxtral WebSocket client with connect/send/receive
This commit is contained in:
parent
e5395017c2
commit
590b0366d3
1 changed files with 90 additions and 0 deletions
90
MyVoxtral/MyVoxtral/Network/VoxtralWebSocketClient.swift
Normal file
90
MyVoxtral/MyVoxtral/Network/VoxtralWebSocketClient.swift
Normal file
|
|
@ -0,0 +1,90 @@
|
|||
import Foundation
|
||||
|
||||
@MainActor
|
||||
final class VoxtralWebSocketClient {
|
||||
private var webSocketTask: URLSessionWebSocketTask?
|
||||
private var session: URLSession?
|
||||
private let encoder = JSONEncoder()
|
||||
|
||||
var onEvent: ((VoxtralEvent) -> Void)?
|
||||
|
||||
func connect(apiKey: String, delayMs: Int) {
|
||||
guard let url = URL(string: "wss://api.mistral.ai/v1/audio/transcriptions/realtime") else { return }
|
||||
|
||||
var request = URLRequest(url: url)
|
||||
request.setValue("Bearer \(apiKey)", forHTTPHeaderField: "Authorization")
|
||||
|
||||
session = URLSession(configuration: .default)
|
||||
webSocketTask = session?.webSocketTask(with: request)
|
||||
webSocketTask?.resume()
|
||||
|
||||
// Send session config
|
||||
let config = SessionUpdateMessage(
|
||||
session: SessionConfig(
|
||||
audioFormat: AudioFormatConfig(),
|
||||
targetStreamingDelayMs: delayMs
|
||||
)
|
||||
)
|
||||
sendJSON(config)
|
||||
|
||||
// Start receiving
|
||||
receiveLoop()
|
||||
}
|
||||
|
||||
func sendAudio(_ pcmData: Data) {
|
||||
let base64 = pcmData.base64EncodedString()
|
||||
let msg = AudioAppendMessage(audio: base64)
|
||||
sendJSON(msg)
|
||||
}
|
||||
|
||||
func flush() {
|
||||
sendJSON(AudioFlushMessage())
|
||||
}
|
||||
|
||||
func disconnect() {
|
||||
sendJSON(AudioEndMessage())
|
||||
webSocketTask?.cancel(with: .normalClosure, reason: nil)
|
||||
webSocketTask = nil
|
||||
session?.invalidateAndCancel()
|
||||
session = nil
|
||||
}
|
||||
|
||||
private func sendJSON<T: Encodable>(_ value: T) {
|
||||
guard let data = try? encoder.encode(value),
|
||||
let string = String(data: data, encoding: .utf8) else { return }
|
||||
webSocketTask?.send(.string(string)) { error in
|
||||
if let error {
|
||||
print("WebSocket send error: \(error)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func receiveLoop() {
|
||||
webSocketTask?.receive { [weak self] result in
|
||||
switch result {
|
||||
case .success(let message):
|
||||
switch message {
|
||||
case .string(let text):
|
||||
if let data = text.data(using: .utf8) {
|
||||
let event = parseVoxtralEvent(from: data)
|
||||
Task { @MainActor in
|
||||
self?.onEvent?(event)
|
||||
}
|
||||
}
|
||||
case .data(let data):
|
||||
let event = parseVoxtralEvent(from: data)
|
||||
Task { @MainActor in
|
||||
self?.onEvent?(event)
|
||||
}
|
||||
@unknown default:
|
||||
break
|
||||
}
|
||||
self?.receiveLoop()
|
||||
case .failure(let error):
|
||||
Task { @MainActor in
|
||||
self?.onEvent?(.error("Connection lost: \(error.localizedDescription)"))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue