Skip to content

Commit

Permalink
update
Browse files Browse the repository at this point in the history
  • Loading branch information
swiftuiux committed Nov 26, 2024
1 parent ac4601f commit cd60e15
Show file tree
Hide file tree
Showing 6 changed files with 120 additions and 62 deletions.
115 changes: 71 additions & 44 deletions Sources/openai-async-image-swiftui/OpenAIAsyncImage.swift
Original file line number Diff line number Diff line change
Expand Up @@ -44,21 +44,32 @@ public struct OpenAIAsyncImage<Content: View, T: IOpenAILoader>: View {
/// Optional custom view builder template
let tpl : ImageProcess?

/// Dall-e model type
let model : DalleModel

// MARK: - Life cycle

/// Initializes a view model for generating images using the OpenAI API with customizable parameters.
/// - Parameters:
/// - prompt: A text description of the desired image(s). The maximum length is 1000 characters
/// - size: The size of the generated images. Must be one of 256x256, 512x512, or 1024x1024
/// - tpl: Custom view builder template
/// - loader: Custom loader conforming to `IOpenAILoader`
/// - prompt: A `Binding` to a `String` that represents a text description of the desired image(s).
/// The maximum length for the prompt is 1000 characters.
/// - size: The size of the generated images, specified as an `OpenAIImageSize`.
/// Defaults to `.dpi256`. Must be one of `.dpi256` (256x256), `.dpi512` (512x512), or `.dpi1024` (1024x1024).
/// - model: The `DalleModel` specifying which model to use for generating the image(s).
/// Defaults to `.dalle2`.
/// - tpl: A custom SwiftUI `ViewBuilder` template for processing or rendering the generated image(s).
/// - loader: A custom loader conforming to the `IOpenAILoader` protocol, responsible for handling
/// the image generation process, such as communicating with the OpenAI API.
public init(
prompt : Binding<String>,
size : OpenAIImageSize = .dpi256,
@ViewBuilder tpl : @escaping ImageProcess,
loader : T
){
prompt: Binding<String>,
size: OpenAIImageSize = .dpi256,
model: DalleModel = .dalle2,
@ViewBuilder tpl: @escaping ImageProcess,
loader: T
) {
self._prompt = prompt
self.size = size
self.model = model
self.tpl = tpl
self.loader = loader
}
Expand Down Expand Up @@ -97,32 +108,41 @@ public struct OpenAIAsyncImage<Content: View, T: IOpenAILoader>: View {
return .loading
}

/// Load using the default loader
/// Loads an image using the default loader.
/// - Parameters:
/// - prompt: The text prompt for generating the image
/// - size: The desired size of the image
/// - Returns: OpenAI image
private func loadImageDefault(_ prompt : String, with size : ImageSize) async throws -> Image{
try await defaultLoader.load(prompt, with: size)
/// - prompt: The text prompt describing the desired image content.
/// - size: The dimensions of the generated image, specified as `ImageSize`.
/// - model: The `DalleModel` specifying the AI model to use for image generation.
/// - Returns: A generated `Image` object if successful.
/// - Throws: An error if the image generation fails.
private func loadImageDefault(
_ prompt: String,
with size: ImageSize,
model: DalleModel
) async throws -> Image {
try await defaultLoader.load(prompt, with: size, model: model)
}
/// Load image using the provided or default loader

/// Loads an image using a provided loader, or falls back to the default loader if none is provided.
/// - Parameters:
/// - prompt: The text prompt for generating the image
/// - size: The desired size of the image
/// - Returns: OpenAI image if successful, otherwise nil
private func loadImage(_ prompt : String, with size : ImageSize) async -> Image?{
do{
if let loader = loader{
return try await loader.load(prompt, with: size)
/// - prompt: The text prompt describing the desired image content.
/// - size: The dimensions of the generated image, specified as `ImageSize`.
/// - model: The `DalleModel` specifying the AI model to use for image generation.
/// - Returns: An `Image` object if successful, or `nil` if the operation fails or is cancelled.
private func loadImage(
_ prompt: String,
with size: ImageSize,
model: DalleModel
) async -> Image? {
do {
if let loader = loader {
return try await loader.load(prompt, with: size, model: model)
}

return try await loadImageDefault(prompt, with: size)
}catch{
if !Task.isCancelled{
return try await loadImageDefault(prompt, with: size, model: model)
} catch {
if !Task.isCancelled {
self.error = error
}

return nil
}
}
Expand Down Expand Up @@ -151,7 +171,7 @@ public struct OpenAIAsyncImage<Content: View, T: IOpenAILoader>: View {
/// - Returns: A task that fetches the OpenAI image
private func getTask() -> Task<Void, Never>{
Task{
if let image = await loadImage(prompt, with: size){
if let image = await loadImage(prompt, with: size, model: model){
await setImage(image)
}
}
Expand All @@ -162,35 +182,42 @@ public struct OpenAIAsyncImage<Content: View, T: IOpenAILoader>: View {

public extension OpenAIAsyncImage where Content == EmptyView, T == OpenAIDefaultLoader{

/// Convenience initializer for default loader without custom view template
/// Convenience initializer for creating an instance with the default loader and no custom view template.
/// - Parameters:
/// - prompt: The text prompt for generating the image
/// - size: The desired size of the image
/// - prompt: A `Binding` to a `String` containing the text prompt that describes the desired image content.
/// - size: The desired size of the generated image, specified as an `OpenAIImageSize`.
/// Defaults to `.dpi256`.
/// - model: The `DalleModel` specifying the AI model to use for image generation. Defaults to `.dalle2`.
init(
prompt : Binding<String>,
size : OpenAIImageSize = .dpi256
){
prompt: Binding<String>,
size: OpenAIImageSize = .dpi256,
model: DalleModel = .dalle2
) {
self._prompt = prompt
self.size = size
self.model = model
self.tpl = nil
self.loader = nil
}
}

public extension OpenAIAsyncImage where T == OpenAIDefaultLoader{

/// Convenience initializer for default loader with custom view template
/// Convenience initializer for creating an instance with the default loader and a custom view template.
/// - Parameters:
/// - prompt: The text prompt for generating the image
/// - size: The desired size of the image
/// - tpl: Custom view template
/// - prompt: A `Binding` to a `String` containing the text prompt that describes the desired image content.
/// - size: The desired size of the generated image, specified as an `OpenAIImageSize`. Defaults to `.dpi256`.
/// - model: The `DalleModel` specifying the AI model to use for image generation. Defaults to `.dalle2`.
/// - tpl: A SwiftUI `@ViewBuilder` closure that provides a custom view template for processing or rendering the generated image.
init(
prompt : Binding<String>,
size : OpenAIImageSize = .dpi256,
@ViewBuilder tpl : @escaping ImageProcess
){
prompt: Binding<String>,
size: OpenAIImageSize = .dpi256,
model: DalleModel = .dalle2,
@ViewBuilder tpl: @escaping ImageProcess
) {
self._prompt = prompt
self.size = size
self.model = model
self.tpl = tpl
self.loader = nil
}
Expand Down
13 changes: 13 additions & 0 deletions Sources/openai-async-image-swiftui/enum/DalleModel.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
//
// DalleModel.swift
// openai-async-image-swiftui
//
// Created by Igor on 26.11.24.
//

public enum DalleModel: String{

case dalle2 = "dall-e-2"

case dalle3 = "dall-e-3"
}
4 changes: 4 additions & 0 deletions Sources/openai-async-image-swiftui/enum/OpenAIImageSize.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,8 @@ public enum OpenAIImageSize: String, Encodable{
case dpi512 = "512x512"

case dpi1024 = "1024x1024"

case dpi1792x1024 = "1792x1024"

case dpi1024x1792 = "1024x1792"
}
3 changes: 3 additions & 0 deletions Sources/openai-async-image-swiftui/model/Input.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ import Foundation
/// Given a prompt and/or an input image, the model will generate a new image
@available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *)
struct Input: Encodable{

/// dall-e model
let model : String

/// A text description of the desired image(s). The maximum length is 1000 characters
let prompt: String
Expand Down
13 changes: 8 additions & 5 deletions Sources/openai-async-image-swiftui/protocol/IOpenAILoader.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,13 @@ import SwiftUI
@available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *)
public protocol IOpenAILoader {

/// Asynchronously loads an image based on a provided text prompt and size
/// Asynchronously generates an image using a given text prompt, size, and model.
/// - Parameters:
/// - prompt: The text prompt describing the desired image
/// - size: The size of the generated image
/// - Returns: The generated OpenAI image
func load(_ prompt: String, with size: OpenAIImageSize) async throws -> Image
/// - prompt: A descriptive text prompt that defines the content of the desired image.
/// - size: The dimensions of the generated image, specified as an `OpenAIImageSize`.
/// - model: The `DalleModel` used for image generation.
/// - Returns: A generated `Image` based on the provided prompt and size.
/// - Throws: An error if the image generation process fails, such as issues with the prompt, model, or network.
func load(_ prompt: String, with size: OpenAIImageSize,
model: DalleModel) async throws -> Image
}
Original file line number Diff line number Diff line change
Expand Up @@ -38,37 +38,45 @@ public final class OpenAIDefaultLoader: IOpenAILoader, Sendable {
client = Http.Proxy(baseURL: url)
}

/// Loads an image from the OpenAI API based on a text prompt
/// Asynchronously loads an image from the OpenAI API using a text prompt and specified parameters.
/// - Parameters:
/// - prompt: The text prompt describing the desired image
/// - size: The size of the generated image
/// - Returns: OpenAI Image
/// - prompt: The text prompt describing the desired image content.
/// - size: The dimensions of the generated image, specified as `OpenAIImageSize`.
/// - model: The `DalleModel` used for generating the image.
/// - Returns: A generated `Image` object based on the prompt and size.
/// - Throws: An `AsyncImageErrors` if the client is undefined, the request fails,
/// or the OpenAI API returns an error.
public func load(
_ prompt: String,
with size: OpenAIImageSize
with size: OpenAIImageSize,
model: DalleModel
) async throws -> Image {

guard let client = client else {
throw AsyncImageErrors.clientIsNotDefined
}

do {
let (path, body, headers) = prepareRequest(prompt: prompt, size: size)
let (path, body, headers) = prepareRequest(prompt: prompt, size: size, model: model)
let result: Http.Response<Output> = try await client.post(path: path, body: body, headers: headers)
return try imageBase64(from: result.value)

} catch {
throw AsyncImageErrors.handleRequest(error)
}
}
/// Prepares the request with the necessary parameters

/// Prepares the API request for generating an image with the given parameters.
/// - Parameters:
/// - prompt: The text prompt describing the desired image
/// - size: The size of the generated image
/// - Returns: A tuple containing the path, body, and headers for the request
private func prepareRequest(prompt: String, size: OpenAIImageSize) -> (String, Input, [String: String]) {
let body = Input(prompt: prompt, size: size, response_format: .b64, n: 1)
/// - prompt: The descriptive text prompt for generating the image.
/// - size: The dimensions of the image to be generated, as `OpenAIImageSize`.
/// - model: The `DalleModel` specifying the AI model to use for generation.
/// - Returns: A tuple containing:
/// - `path`: The API endpoint path as a `String`.
/// - `body`: The request payload as an `Input` object, containing model, prompt, size, and other parameters.
/// - `headers`: A dictionary of HTTP headers required for the request.
private func prepareRequest(prompt: String, size: OpenAIImageSize, model: DalleModel) -> (String, Input, [String: String]) {
let body = Input(model: model.rawValue, prompt: prompt, size: size, response_format: .b64, n: 1)
let headers = ["Content-Type": "application/json", "Authorization": "Bearer \(endpoint.apiKey)"]
let path = endpoint.path
return (path, body, headers)
Expand Down

0 comments on commit cd60e15

Please sign in to comment.