mirror of
https://github.com/Dimillian/IceCubesApp.git
synced 2024-05-19 00:38:10 +00:00
134 lines
4.3 KiB
Swift
134 lines
4.3 KiB
Swift
import Foundation
|
||
|
||
protocol OpenAIRequest: Encodable {
|
||
var model: String { get }
|
||
}
|
||
|
||
public struct OpenAIClient {
|
||
private let endpoint: URL = .init(string: "https://icecubesrelay.fly.dev/openai")!
|
||
|
||
private var encoder: JSONEncoder {
|
||
let encoder = JSONEncoder()
|
||
encoder.keyEncodingStrategy = .convertToSnakeCase
|
||
return encoder
|
||
}
|
||
|
||
private var decoder: JSONDecoder {
|
||
let decoder = JSONDecoder()
|
||
decoder.keyDecodingStrategy = .convertFromSnakeCase
|
||
return decoder
|
||
}
|
||
|
||
public struct ChatRequest: OpenAIRequest {
|
||
public struct Message: Encodable {
|
||
public let role = "user"
|
||
public let content: String
|
||
}
|
||
|
||
let model = "gpt-3.5-turbo"
|
||
let messages: [Message]
|
||
|
||
let temperature: CGFloat
|
||
|
||
public init(content: String, temperature: CGFloat) {
|
||
messages = [.init(content: content)]
|
||
self.temperature = temperature
|
||
}
|
||
}
|
||
|
||
public struct VisionRequest: OpenAIRequest {
|
||
public struct Message: Encodable {
|
||
public struct MessageContent: Encodable {
|
||
public struct ImageUrl: Encodable {
|
||
public let url: URL
|
||
}
|
||
public let type: String
|
||
public let text: String?
|
||
public let imageUrl: ImageUrl?
|
||
}
|
||
|
||
public let role = "user"
|
||
public let content: [MessageContent]
|
||
}
|
||
|
||
let model = "gpt-4-vision-preview"
|
||
let messages: [Message]
|
||
let maxTokens = 50
|
||
}
|
||
|
||
public enum Prompt {
|
||
case correct(input: String)
|
||
case shorten(input: String)
|
||
case emphasize(input: String)
|
||
case addTags(input: String)
|
||
case insertTags(input: String)
|
||
case imageDescription(image: URL)
|
||
|
||
var request: OpenAIRequest {
|
||
switch self {
|
||
case let .correct(input):
|
||
ChatRequest(content: "Fix the spelling and grammar mistakes in the following text: \(input)", temperature: 0.2)
|
||
case let .addTags(input):
|
||
ChatRequest(content: "Replace relevant words with camel-cased hashtags in the following text. Don't try to search for context or add hashtags if there is not enough context: \(input)", temperature: 0.1)
|
||
case let .insertTags(input):
|
||
ChatRequest(content: "Return the input with added camel-cased hashtags at the end of the input. Don't try to search for context or add hashtags if there is not enough context: \(input)", temperature: 0.2)
|
||
case let .shorten(input):
|
||
ChatRequest(content: "Make a shorter version of this text: \(input)", temperature: 0.5)
|
||
case let .emphasize(input):
|
||
ChatRequest(content: "Make this text catchy, more fun: \(input)", temperature: 1)
|
||
case let .imageDescription(image):
|
||
VisionRequest(messages: [.init(content: [.init(type: "text", text: "What’s in this image? Be brief, it's for image alt description on a social network. Don't write in the first person.", imageUrl: nil)
|
||
, .init(type: "image_url", text: nil, imageUrl: .init(url: image))])])
|
||
}
|
||
}
|
||
}
|
||
|
||
public struct Response: Decodable {
|
||
public struct Choice: Decodable {
|
||
public struct Message: Decodable {
|
||
public let role: String
|
||
public let content: String
|
||
}
|
||
|
||
public let message: Message?
|
||
}
|
||
|
||
public let choices: [Choice]
|
||
|
||
public var trimmedText: String {
|
||
guard var text = choices.first?.message?.content else {
|
||
return ""
|
||
}
|
||
while text.first?.isNewline == true || text.first?.isWhitespace == true {
|
||
text.removeFirst()
|
||
}
|
||
return text
|
||
}
|
||
}
|
||
|
||
public init() {}
|
||
|
||
public func request(_ prompt: Prompt) async throws -> Response {
|
||
do {
|
||
let jsonData = try encoder.encode(prompt.request)
|
||
var request = URLRequest(url: endpoint)
|
||
request.httpMethod = "POST"
|
||
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
|
||
request.httpBody = jsonData
|
||
let (result, _) = try await URLSession.shared.data(for: request)
|
||
let response = try decoder.decode(Response.self, from: result)
|
||
return response
|
||
} catch {
|
||
throw error
|
||
}
|
||
}
|
||
}
|
||
|
||
extension OpenAIClient: Sendable {}
|
||
extension OpenAIClient.Prompt: Sendable {}
|
||
extension OpenAIClient.ChatRequest: Sendable {}
|
||
extension OpenAIClient.ChatRequest.Message: Sendable {}
|
||
extension OpenAIClient.Response: Sendable {}
|
||
extension OpenAIClient.Response.Choice: Sendable {}
|
||
extension OpenAIClient.Response.Choice.Message: Sendable {}
|