feat:mac os
This commit is contained in:
1 parent
136bf7cfa3
commit
3400e2b465
18 files changed
+1129
No files matched your search
@@ -0,0 +1,110 @@
|
||||
import Foundation
|
||||
|
||||
#if canImport(FoundationNetworking)
|
||||
import FoundationNetworking
|
||||
#endif
|
||||
|
||||
enum TranscriptionError: LocalizedError {
|
||||
case invalidEndpoint
|
||||
case invalidResponse
|
||||
case http(Int)
|
||||
|
||||
var errorDescription: String? {
|
||||
switch self {
|
||||
case .invalidEndpoint: "The transcription endpoint is invalid."
|
||||
case .invalidResponse: "The server response did not contain a transcript."
|
||||
case .http(let status): "The server returned HTTP \(status)."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct TranscriptionClient {
|
||||
static func endpointURL(from value: String) -> URL? {
|
||||
var endpoint = value.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
while endpoint.hasSuffix("/") { endpoint.removeLast() }
|
||||
if !endpoint.contains("/v1/audio/transcriptions") {
|
||||
endpoint += "/v1/audio/transcriptions"
|
||||
}
|
||||
guard let url = URL(string: endpoint),
|
||||
let scheme = url.scheme?.lowercased(),
|
||||
scheme == "http" || scheme == "https",
|
||||
url.host != nil
|
||||
else { return nil }
|
||||
return url
|
||||
}
|
||||
|
||||
func transcribe(
|
||||
recording: URL,
|
||||
endpoint: String,
|
||||
model: String,
|
||||
key: String,
|
||||
prompt: String
|
||||
) async throws -> String {
|
||||
guard let url = Self.endpointURL(from: endpoint) else {
|
||||
throw TranscriptionError.invalidEndpoint
|
||||
}
|
||||
|
||||
let boundary = "transui-\(UUID().uuidString)"
|
||||
var request = URLRequest(url: url)
|
||||
request.httpMethod = "POST"
|
||||
request.timeoutInterval = 300
|
||||
request.setValue(
|
||||
"multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type")
|
||||
request.setValue("application/json", forHTTPHeaderField: "Accept")
|
||||
if !key.isEmpty {
|
||||
request.setValue("Bearer \(key)", forHTTPHeaderField: "Authorization")
|
||||
}
|
||||
request.httpBody = try multipartBody(
|
||||
boundary: boundary,
|
||||
recording: recording,
|
||||
model: model,
|
||||
prompt: prompt
|
||||
)
|
||||
|
||||
let (data, response) = try await URLSession.shared.data(for: request)
|
||||
guard let http = response as? HTTPURLResponse else {
|
||||
throw TranscriptionError.invalidResponse
|
||||
}
|
||||
guard (200..<300).contains(http.statusCode) else {
|
||||
throw TranscriptionError.http(http.statusCode)
|
||||
}
|
||||
guard data.count <= 1_048_576 else {
|
||||
throw TranscriptionError.invalidResponse
|
||||
}
|
||||
guard let object = try JSONSerialization.jsonObject(with: data) as? [String: Any],
|
||||
let text = object["text"] as? String
|
||||
else {
|
||||
throw TranscriptionError.invalidResponse
|
||||
}
|
||||
return TranscriptCleaner.clean(text)
|
||||
}
|
||||
|
||||
private func multipartBody(
|
||||
boundary: String,
|
||||
recording: URL,
|
||||
model: String,
|
||||
prompt: String
|
||||
) throws -> Data {
|
||||
var data = Data()
|
||||
func field(_ name: String, _ value: String) {
|
||||
data.append("--\(boundary)\r\n")
|
||||
data.append("Content-Disposition: form-data; name=\"\(name)\"\r\n\r\n")
|
||||
data.append("\(value)\r\n")
|
||||
}
|
||||
field("response_format", "json")
|
||||
if !model.isEmpty { field("model", model) }
|
||||
if !prompt.isEmpty { field("prompt", prompt) }
|
||||
data.append("--\(boundary)\r\n")
|
||||
data.append("Content-Disposition: form-data; name=\"file\"; filename=\"recording.wav\"\r\n")
|
||||
data.append("Content-Type: audio/wav\r\n\r\n")
|
||||
data.append(try Data(contentsOf: recording))
|
||||
data.append("\r\n--\(boundary)--\r\n")
|
||||
return data
|
||||
}
|
||||
}
|
||||
|
||||
extension Data {
|
||||
fileprivate mutating func append(_ string: String) {
|
||||
append(contentsOf: string.utf8)
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user