Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ public struct AssistantStartThreadScreen: View {
.padding()
Button {
Task {
try await threadProvider.createMessage(threadID: threadID, parameters: .init(role: .user, content: prompt))
try await threadProvider.createMessage(threadID: threadID, parameters: .init(role: .user, content: .stringContent(prompt)))
if let message = threadProvider.message {
tutorialStage = .createRunAndStream(message: message)
}
Expand Down Expand Up @@ -125,7 +125,7 @@ public struct AssistantStartThreadScreen: View {
.padding()
Button {
Task {
try await threadProvider.createMessage(threadID: threadID, parameters: .init(role: .user, content: prompt))
try await threadProvider.createMessage(threadID: threadID, parameters: .init(role: .user, content: .arrayContent([.text(.init(text: prompt))])))
threadProvider.messageText = ""
threadProvider.toolOuptutMessage = ""
try await threadProvider.createRunAndStreamMessage(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ struct FileAttachmentView: View {
parameters: FileParameters)
-> some View
{
AttachmentView(fileName: fileObject?.filename ?? parameters.fileName, actionTrigger: $deleted, isLoading: fileObject == nil || deleted)
AttachmentView(fileName: fileObject?.filename ?? parameters.fileName ?? "", actionTrigger: $deleted, isLoading: fileObject == nil || deleted)
.disabled(fileObject == nil)
.opacity(fileObject == nil ? 0.3 : 1)
.onFirstAppear {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ extension FileParameters: Equatable, Identifiable {
}

public var id: String {
fileName
fileName ?? ""
}
}

Expand Down
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2339,8 +2339,8 @@ public struct MessageParameter: Encodable {
/// user: Indicates the message is sent by an actual user and should be used in most cases to represent user-generated messages.
/// assistant: Indicates the message is generated by the assistant. Use this value to insert messages from the assistant into the conversation.
let role: String
/// The content of the message.
let content: String
/// The content of the message, which can be a string or an array of content parts (text, image URL, image file).
let content: Content
/// A list of files attached to the message, and the tools they should be added to.
let attachments: [MessageAttachment]?
/// Set of 16 key-value pairs that can be attached to an object. This can be useful for storing additional information about the object in a structured format. Keys can be a maximum of 64 characters long and values can be a maxium of 512 characters long.
Expand Down Expand Up @@ -2491,7 +2491,7 @@ Create Message.
```swift
let threadID = "thread_abc123"
let prompt = "Give me some ideas for a birthday party."
let parameters = MessageParameter(role: "user", content: prompt")
let parameters = MessageParameter(role: "user", content: .stringContent(prompt)")
let message = try await service.createMessage(threadID: threadID, parameters: parameters)
```

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ struct MultipartFormDataBuilder {

enum MultipartFormDataEntry {

case file(paramName: String, fileName: String, fileData: Data, contentType: String)
case file(paramName: String, fileName: String?, fileData: Data, contentType: String)
case string(paramName: String, value: Any?)
}

Expand All @@ -48,7 +48,11 @@ extension MultipartFormDataEntry {
switch self {
case .file(let paramName, let fileName, let fileData, let contentType):
body.append("--\(boundary)\r\n")
body.append("Content-Disposition: form-data; name=\"\(paramName)\"; filename=\"\(fileName)\"\r\n")
if let fileName = fileName {
body.append("Content-Disposition: form-data; name=\"\(paramName)\"; filename=\"\(fileName)\"\r\n")
} else {
body.append("Content-Disposition: form-data; name=\"\(paramName)\"\r\n")
}
body.append("Content-Type: \(contentType)\r\n\r\n")
body.append(fileData)
body.append("\r\n")
Expand Down
4 changes: 2 additions & 2 deletions Sources/OpenAI/Public/Parameters/File/FileParameter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import Foundation
public struct FileParameters: Encodable {

/// The name of the file asset is not documented in OpenAI's official documentation; however, it is essential for constructing the multipart request.
public let fileName: String
public let fileName: String?
/// The file object (not file name) to be uploaded.
/// If the purpose is set to "fine-tune", the file will be used for fine-tuning.
public let file: Data
Expand All @@ -20,7 +20,7 @@ public struct FileParameters: Encodable {
public let purpose: String

public init(
fileName: String,
fileName: String?,
file: Data,
purpose: String)
{
Expand Down
133 changes: 119 additions & 14 deletions Sources/OpenAI/Public/Parameters/Message/MessageParameter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,38 +14,143 @@ public struct MessageParameter: Encodable {
/// user: Indicates the message is sent by an actual user and should be used in most cases to represent user-generated messages.
/// assistant: Indicates the message is generated by the assistant. Use this value to insert messages from the assistant into the conversation.
let role: String
/// The content of the message.
let content: String
/// The content of the message, which can be a string or an array of content parts (text, image URL, image file).
let content: Content
/// A list of files attached to the message, and the tools they should be added to.
let attachments: [MessageAttachment]?
/// Set of 16 key-value pairs that can be attached to an object. This can be useful for storing additional information about the object in a structured format. Keys can be a maximum of 64 characters long and values can be a maxium of 512 characters long.
let metadata: [String: String]?

public enum Role: String {
/// Indicates the message is sent by an actual user and should be used in most cases to represent user-generated messages.
case user
/// Indicates the message is generated by the assistant. Use this value to insert messages from the assistant into the conversation.
case assistant
}

enum CodingKeys: String, CodingKey {
case role
case content
case attachments
case metadata
public enum DetailLevel: String, Encodable {
case low
case high
case auto = "auto"
}

/// Wrapper to handle both a string content or an array of content parts.
public enum Content: Encodable {
case stringContent(String)
case arrayContent([ContentItem])

public func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
switch self {
case .stringContent(let text):
try container.encode(text)
case .arrayContent(let contentArray):
try container.encode(contentArray)
}
}
}

/// Enum to represent different content parts (text, image URL, image file).
public enum ContentItem: Encodable {
case text(Text)
case imageURL(ImageURL)
case imageFile(ImageFile)

public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
switch self {
case .text(let textContent):
try container.encode("text", forKey: .type)
try container.encode(textContent, forKey: .text)
case .imageURL(let imageURLContent):
try container.encode("image_url", forKey: .type)
try container.encode(imageURLContent, forKey: .imageURL)
case .imageFile(let imageFileContent):
try container.encode("image_file", forKey: .type)
try container.encode(imageFileContent, forKey: .imageFile)
}
}

enum CodingKeys: String, CodingKey {
case type
case text
case imageURL = "image_url"
case imageFile = "image_file"
}
}

/// Struct representing a text content part.
public struct Text: Encodable {
let text: String

public init(text: String) {
self.text = text
}

enum CodingKeys: String, CodingKey {
case type
case text
}

public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode("text", forKey: .type)
try container.encode(text, forKey: .text)
}
}

/// References an image URL in the content of a message.
public struct ImageURL: Encodable {

/// The external URL of the image, must be a supported image types: jpeg, jpg, png, gif, webp.
let url: String

/// Specifies the detail level of the image. low uses fewer tokens, you can opt in to high resolution using high. Default value is auto
let detail: DetailLevel?

public init(
url: String,
detail: DetailLevel? = nil)
{
self.url = url
self.detail = detail
}

enum CodingKeys: String, CodingKey {
case url
case detail
}
}

/// References an image [File](https://platform.openai.com/docs/api-reference/files) in the content of a message.
public struct ImageFile: Encodable {

/// The [File](https://platform.openai.com/docs/api-reference/files) ID of the image in the message content. Set purpose="vision" when uploading the File if you need to later display the file content.
let fileId: String
/// Specifies the detail level of the image if specified by the user. low uses fewer tokens, you can opt in to high resolution using high.
let detail: DetailLevel?

public init(
fileId: String,
detail: DetailLevel? = nil)
{
self.fileId = fileId
self.detail = detail
}

enum CodingKeys: String, CodingKey {
case fileId = "file_id"
case detail
}
}

public init(
role: Role,
content: String,
content: Content,
attachments: [MessageAttachment]? = nil,
metadata: [String : String]? = nil)
{
metadata: [String : String]? = nil
) {
self.role = role.rawValue
self.content = content
self.attachments = attachments
self.metadata = metadata
}
}