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