Skip to content
22 changes: 22 additions & 0 deletions FirebaseAI/Sources/APIMethod.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@

// Copyright 2025 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

import Foundation

enum APIMethod: String {
case generateContent
case streamGenerateContent
case countTokens
}
79 changes: 9 additions & 70 deletions FirebaseAI/Sources/Chat.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,35 +19,21 @@ import Foundation
@available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *)
public final class Chat: Sendable {
private let model: GenerativeModel
private let _history: History

/// Initializes a new chat representing a 1:1 conversation between model and user.
init(model: GenerativeModel, history: [ModelContent]) {
self.model = model
self.history = history
_history = History(history: history)
}

private let historyLock = NSLock()
private nonisolated(unsafe) var _history: [ModelContent] = []
/// The previous content from the chat that has been successfully sent and received from the
/// model. This will be provided to the model for each message sent as context for the discussion.
public var history: [ModelContent] {
get {
historyLock.withLock { _history }
return _history.history
}
set {
historyLock.withLock { _history = newValue }
}
}

private func appendHistory(contentsOf: [ModelContent]) {
historyLock.withLock {
_history.append(contentsOf: contentsOf)
}
}

private func appendHistory(_ newElement: ModelContent) {
historyLock.withLock {
_history.append(newElement)
_history.history = newValue
}
}

Expand Down Expand Up @@ -87,8 +73,8 @@ public final class Chat: Sendable {
let toAdd = ModelContent(role: "model", parts: reply.parts)

// Append the request and successful result to history, then return the value.
appendHistory(contentsOf: newContent)
appendHistory(toAdd)
_history.append(contentsOf: newContent)
_history.append(toAdd)
return result
}

Expand Down Expand Up @@ -136,63 +122,16 @@ public final class Chat: Sendable {
}

// Save the request.
appendHistory(contentsOf: newContent)
_history.append(contentsOf: newContent)

// Aggregate the content to add it to the history before we finish.
let aggregated = self.aggregatedChunks(aggregatedContent)
self.appendHistory(aggregated)
let aggregated = self._history.aggregatedChunks(aggregatedContent)
self._history.append(aggregated)
continuation.finish()
}
}
}

private func aggregatedChunks(_ chunks: [ModelContent]) -> ModelContent {
var parts: [InternalPart] = []
var combinedText = ""
var combinedThoughts = ""

func flush() {
if !combinedThoughts.isEmpty {
parts.append(InternalPart(.text(combinedThoughts), isThought: true, thoughtSignature: nil))
combinedThoughts = ""
}
if !combinedText.isEmpty {
parts.append(InternalPart(.text(combinedText), isThought: nil, thoughtSignature: nil))
combinedText = ""
}
}

// Loop through all the parts, aggregating the text.
for part in chunks.flatMap({ $0.internalParts }) {
// Only text parts may be combined.
if case let .text(text) = part.data, part.thoughtSignature == nil {
// Thought summaries must not be combined with regular text.
if part.isThought ?? false {
// If we were combining regular text, flush it before handling "thoughts".
if !combinedText.isEmpty {
flush()
}
combinedThoughts += text
} else {
// If we were combining "thoughts", flush it before handling regular text.
if !combinedThoughts.isEmpty {
flush()
}
combinedText += text
}
} else {
// This is a non-combinable part (not text), flush any pending text.
flush()
parts.append(part)
}
}

// Flush any remaining text.
flush()

return ModelContent(role: "model", parts: parts)
}

/// Populates the `role` field with `user` if it doesn't exist. Required in chat sessions.
private func populateContentRole(_ content: ModelContent) -> ModelContent {
if content.role != nil {
Expand Down
22 changes: 22 additions & 0 deletions FirebaseAI/Sources/FirebaseAI.swift
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,28 @@ public final class FirebaseAI: Sendable {
)
}

/// Initializes a new `TemplateGenerativeModel`.
///
/// - Returns: A new `TemplateGenerativeModel` instance.
public func templateGenerativeModel() -> TemplateGenerativeModel {
return TemplateGenerativeModel(
generativeAIService: GenerativeAIService(firebaseInfo: firebaseInfo,
urlSession: GenAIURLSession.default),
apiConfig: apiConfig
)
}

/// Initializes a new `TemplateImagenModel`.
///
/// - Returns: A new `TemplateImagenModel` instance.
public func templateImagenModel() -> TemplateImagenModel {
return TemplateImagenModel(
generativeAIService: GenerativeAIService(firebaseInfo: firebaseInfo,
urlSession: GenAIURLSession.default),
apiConfig: apiConfig
)
}

/// **[Public Preview]** Initializes a ``LiveGenerativeModel`` with the given parameters.
///
/// > Warning: Using the Firebase AI Logic SDKs with the Gemini Live API is in Public
Expand Down
9 changes: 0 additions & 9 deletions FirebaseAI/Sources/GenerateContentRequest.swift
Original file line number Diff line number Diff line change
Expand Up @@ -60,15 +60,6 @@ extension GenerateContentRequest: Encodable {
}
}

@available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *)
extension GenerateContentRequest {
enum APIMethod: String {
case generateContent
case streamGenerateContent
case countTokens
}
}

@available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *)
extension GenerateContentRequest: GenerativeAIRequest {
typealias Response = GenerateContentResponse
Expand Down
58 changes: 58 additions & 0 deletions FirebaseAI/Sources/GenerateImagesRequest.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@

// Copyright 2025 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

import Foundation

@available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *)
public class GenerateImagesRequest: @unchecked Sendable, GenerativeAIRequest {
public typealias Response = ImagenGenerationResponse<ImagenInlineImage>

public var url: URL {
var urlString =
"\(apiConfig.service.endpoint.rawValue)/\(apiConfig.version.rawValue)/projects/\(projectID)"
if case let .vertexAI(_, location) = apiConfig.service {
urlString += "/locations/\(location)"
}
let templateName = template.hasSuffix(".prompt") ? template : "\(template).prompt"
urlString += "/templates/\(templateName):\(ImageAPIMethod.generateImages.rawValue)"
return URL(string: urlString)!
}

public let options: RequestOptions

let apiConfig: APIConfig

let template: String
let variables: [String: TemplateVariable]
let projectID: String

init(template: String, variables: [String: TemplateVariable], projectID: String,
apiConfig: APIConfig, options: RequestOptions) {
self.apiConfig = apiConfig
self.options = options
self.template = template
self.variables = variables
self.projectID = projectID
}

enum CodingKeys: String, CodingKey {
case variables = "inputs"
}

public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(variables, forKey: .variables)
}
}
2 changes: 1 addition & 1 deletion FirebaseAI/Sources/GenerativeAIService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
/// The Firebase SDK version in the format `fire/<version>`.
static let firebaseVersionTag = "fire/\(FirebaseVersion())"

private let firebaseInfo: FirebaseInfo
let firebaseInfo: FirebaseInfo

private let urlSession: URLSession

Expand Down Expand Up @@ -61,7 +61,7 @@
)
}

throw parseError(responseData: data)

Check failure on line 64 in FirebaseAI/Sources/GenerativeAIService.swift

View workflow job for this annotation

GitHub Actions / spm / spm (macos-15, Xcode_16.4, catalyst)

testSendMessage, failed: caught error: "BackendError(httpResponseCode: 400, message: "API key not valid. Please pass a valid API key.", status: FirebaseAI.RPCStatus.invalidArgument, details: [FirebaseAI.ErrorDetails(type: "type.googleapis.com/google.rpc.ErrorInfo", reason: Optional("API_KEY_INVALID"), domain: Optional("googleapis.com"), metadata: Optional(["service": "firebasevertexai.googleapis.com"])), FirebaseAI.ErrorDetails(type: "type.googleapis.com/google.rpc.LocalizedMessage", reason: nil, domain: nil, metadata: nil)])"

Check failure on line 64 in FirebaseAI/Sources/GenerativeAIService.swift

View workflow job for this annotation

GitHub Actions / spm / spm (macos-15, Xcode_16.4, catalyst)

testSendMessage, failed: caught error: "BackendError(httpResponseCode: 400, message: "API key not valid. Please pass a valid API key.", status: FirebaseAI.RPCStatus.invalidArgument, details: [FirebaseAI.ErrorDetails(type: "type.googleapis.com/google.rpc.ErrorInfo", reason: Optional("API_KEY_INVALID"), domain: Optional("googleapis.com"), metadata: Optional(["service": "firebasevertexai.googleapis.com"])), FirebaseAI.ErrorDetails(type: "type.googleapis.com/google.rpc.LocalizedMessage", reason: nil, domain: nil, metadata: nil)])"

Check failure on line 64 in FirebaseAI/Sources/GenerativeAIService.swift

View workflow job for this annotation

GitHub Actions / spm / spm (macos-15, Xcode_16.4, catalyst)

testSendMessage, failed: caught error: "BackendError(httpResponseCode: 400, message: "API key not valid. Please pass a valid API key.", status: FirebaseAI.RPCStatus.invalidArgument, details: [FirebaseAI.ErrorDetails(type: "type.googleapis.com/google.rpc.ErrorInfo", reason: Optional("API_KEY_INVALID"), domain: Optional("googleapis.com"), metadata: Optional(["service": "firebasevertexai.googleapis.com"])), FirebaseAI.ErrorDetails(type: "type.googleapis.com/google.rpc.LocalizedMessage", reason: nil, domain: nil, metadata: nil)])"

Check failure on line 64 in FirebaseAI/Sources/GenerativeAIService.swift

View workflow job for this annotation

GitHub Actions / spm / spm (macos-15, Xcode_16.4, tvOS)

testSendMessage, failed: caught error: "BackendError(httpResponseCode: 400, message: "API key not valid. Please pass a valid API key.", status: FirebaseAI.RPCStatus.invalidArgument, details: [FirebaseAI.ErrorDetails(type: "type.googleapis.com/google.rpc.ErrorInfo", reason: Optional("API_KEY_INVALID"), domain: Optional("googleapis.com"), metadata: Optional(["service": "firebasevertexai.googleapis.com"])), FirebaseAI.ErrorDetails(type: "type.googleapis.com/google.rpc.LocalizedMessage", reason: nil, domain: nil, metadata: nil)])"

Check failure on line 64 in FirebaseAI/Sources/GenerativeAIService.swift

View workflow job for this annotation

GitHub Actions / spm / spm (macos-15, Xcode_16.4, tvOS)

testSendMessage, failed: caught error: "BackendError(httpResponseCode: 400, message: "API key not valid. Please pass a valid API key.", status: FirebaseAI.RPCStatus.invalidArgument, details: [FirebaseAI.ErrorDetails(type: "type.googleapis.com/google.rpc.ErrorInfo", reason: Optional("API_KEY_INVALID"), domain: Optional("googleapis.com"), metadata: Optional(["service": "firebasevertexai.googleapis.com"])), FirebaseAI.ErrorDetails(type: "type.googleapis.com/google.rpc.LocalizedMessage", reason: nil, domain: nil, metadata: nil)])"

Check failure on line 64 in FirebaseAI/Sources/GenerativeAIService.swift

View workflow job for this annotation

GitHub Actions / spm / spm (macos-15, Xcode_16.4, tvOS)

testSendMessage, failed: caught error: "BackendError(httpResponseCode: 400, message: "API key not valid. Please pass a valid API key.", status: FirebaseAI.RPCStatus.invalidArgument, details: [FirebaseAI.ErrorDetails(type: "type.googleapis.com/google.rpc.ErrorInfo", reason: Optional("API_KEY_INVALID"), domain: Optional("googleapis.com"), metadata: Optional(["service": "firebasevertexai.googleapis.com"])), FirebaseAI.ErrorDetails(type: "type.googleapis.com/google.rpc.LocalizedMessage", reason: nil, domain: nil, metadata: nil)])"

Check failure on line 64 in FirebaseAI/Sources/GenerativeAIService.swift

View workflow job for this annotation

GitHub Actions / spm / spm (macos-14, Xcode_16.2, iOS)

testSendMessage, failed: caught error: "BackendError(httpResponseCode: 400, message: "API key not valid. Please pass a valid API key.", status: FirebaseAI.RPCStatus.invalidArgument, details: [FirebaseAI.ErrorDetails(type: "type.googleapis.com/google.rpc.ErrorInfo", reason: Optional("API_KEY_INVALID"), domain: Optional("googleapis.com"), metadata: Optional(["service": "firebasevertexai.googleapis.com"])), FirebaseAI.ErrorDetails(type: "type.googleapis.com/google.rpc.LocalizedMessage", reason: nil, domain: nil, metadata: nil)])"

Check failure on line 64 in FirebaseAI/Sources/GenerativeAIService.swift

View workflow job for this annotation

GitHub Actions / spm / spm (macos-14, Xcode_16.2, iOS)

testSendMessage, failed: caught error: "BackendError(httpResponseCode: 400, message: "API key not valid. Please pass a valid API key.", status: FirebaseAI.RPCStatus.invalidArgument, details: [FirebaseAI.ErrorDetails(type: "type.googleapis.com/google.rpc.ErrorInfo", reason: Optional("API_KEY_INVALID"), domain: Optional("googleapis.com"), metadata: Optional(["service": "firebasevertexai.googleapis.com"])), FirebaseAI.ErrorDetails(type: "type.googleapis.com/google.rpc.LocalizedMessage", reason: nil, domain: nil, metadata: nil)])"

Check failure on line 64 in FirebaseAI/Sources/GenerativeAIService.swift

View workflow job for this annotation

GitHub Actions / spm / spm (macos-15, Xcode_16.4, macOS)

testSendMessage, failed: caught error: "BackendError(httpResponseCode: 400, message: "API key not valid. Please pass a valid API key.", status: FirebaseAI.RPCStatus.invalidArgument, details: [FirebaseAI.ErrorDetails(type: "type.googleapis.com/google.rpc.ErrorInfo", reason: Optional("API_KEY_INVALID"), domain: Optional("googleapis.com"), metadata: Optional(["service": "firebasevertexai.googleapis.com"])), FirebaseAI.ErrorDetails(type: "type.googleapis.com/google.rpc.LocalizedMessage", reason: nil, domain: nil, metadata: nil)])"

Check failure on line 64 in FirebaseAI/Sources/GenerativeAIService.swift

View workflow job for this annotation

GitHub Actions / spm / spm (macos-15, Xcode_16.4, macOS)

testSendMessage, failed: caught error: "BackendError(httpResponseCode: 400, message: "API key not valid. Please pass a valid API key.", status: FirebaseAI.RPCStatus.invalidArgument, details: [FirebaseAI.ErrorDetails(type: "type.googleapis.com/google.rpc.ErrorInfo", reason: Optional("API_KEY_INVALID"), domain: Optional("googleapis.com"), metadata: Optional(["service": "firebasevertexai.googleapis.com"])), FirebaseAI.ErrorDetails(type: "type.googleapis.com/google.rpc.LocalizedMessage", reason: nil, domain: nil, metadata: nil)])"

Check failure on line 64 in FirebaseAI/Sources/GenerativeAIService.swift

View workflow job for this annotation

GitHub Actions / spm / spm (macos-15, Xcode_16.4, macOS)

testSendMessage, failed: caught error: "BackendError(httpResponseCode: 400, message: "API key not valid. Please pass a valid API key.", status: FirebaseAI.RPCStatus.invalidArgument, details: [FirebaseAI.ErrorDetails(type: "type.googleapis.com/google.rpc.ErrorInfo", reason: Optional("API_KEY_INVALID"), domain: Optional("googleapis.com"), metadata: Optional(["service": "firebasevertexai.googleapis.com"])), FirebaseAI.ErrorDetails(type: "type.googleapis.com/google.rpc.LocalizedMessage", reason: nil, domain: nil, metadata: nil)])"

Check failure on line 64 in FirebaseAI/Sources/GenerativeAIService.swift

View workflow job for this annotation

GitHub Actions / spm / spm (macos-15, Xcode_16.4, watchOS)

testSendMessage, failed: caught error: "BackendError(httpResponseCode: 400, message: "API key not valid. Please pass a valid API key.", status: FirebaseAI.RPCStatus.invalidArgument, details: [FirebaseAI.ErrorDetails(type: "type.googleapis.com/google.rpc.ErrorInfo", reason: Optional("API_KEY_INVALID"), domain: Optional("googleapis.com"), metadata: Optional(["service": "firebasevertexai.googleapis.com"])), FirebaseAI.ErrorDetails(type: "type.googleapis.com/google.rpc.LocalizedMessage", reason: nil, domain: nil, metadata: nil)])"

Check failure on line 64 in FirebaseAI/Sources/GenerativeAIService.swift

View workflow job for this annotation

GitHub Actions / spm / spm (macos-15, Xcode_16.4, watchOS)

testSendMessage, failed: caught error: "BackendError(httpResponseCode: 400, message: "API key not valid. Please pass a valid API key.", status: FirebaseAI.RPCStatus.invalidArgument, details: [FirebaseAI.ErrorDetails(type: "type.googleapis.com/google.rpc.ErrorInfo", reason: Optional("API_KEY_INVALID"), domain: Optional("googleapis.com"), metadata: Optional(["service": "firebasevertexai.googleapis.com"])), FirebaseAI.ErrorDetails(type: "type.googleapis.com/google.rpc.LocalizedMessage", reason: nil, domain: nil, metadata: nil)])"

Check failure on line 64 in FirebaseAI/Sources/GenerativeAIService.swift

View workflow job for this annotation

GitHub Actions / spm / spm (macos-15, Xcode_16.4, iOS)

testSendMessage, failed: caught error: "BackendError(httpResponseCode: 400, message: "API key not valid. Please pass a valid API key.", status: FirebaseAI.RPCStatus.invalidArgument, details: [FirebaseAI.ErrorDetails(type: "type.googleapis.com/google.rpc.ErrorInfo", reason: Optional("API_KEY_INVALID"), domain: Optional("googleapis.com"), metadata: Optional(["service": "firebasevertexai.googleapis.com"])), FirebaseAI.ErrorDetails(type: "type.googleapis.com/google.rpc.LocalizedMessage", reason: nil, domain: nil, metadata: nil)])"

Check failure on line 64 in FirebaseAI/Sources/GenerativeAIService.swift

View workflow job for this annotation

GitHub Actions / spm / spm (macos-15, Xcode_16.4, visionOS)

testSendMessage, failed: caught error: "BackendError(httpResponseCode: 400, message: "API key not valid. Please pass a valid API key.", status: FirebaseAI.RPCStatus.invalidArgument, details: [FirebaseAI.ErrorDetails(type: "type.googleapis.com/google.rpc.ErrorInfo", reason: Optional("API_KEY_INVALID"), domain: Optional("googleapis.com"), metadata: Optional(["service": "firebasevertexai.googleapis.com"])), FirebaseAI.ErrorDetails(type: "type.googleapis.com/google.rpc.LocalizedMessage", reason: nil, domain: nil, metadata: nil)])"

Check failure on line 64 in FirebaseAI/Sources/GenerativeAIService.swift

View workflow job for this annotation

GitHub Actions / spm / spm (macos-26, Xcode_26.0, iOS)

testSendMessage, failed: caught error: "BackendError(httpResponseCode: 400, message: "API key not valid. Please pass a valid API key.", status: FirebaseAI.RPCStatus.invalidArgument, details: [FirebaseAI.ErrorDetails(type: "type.googleapis.com/google.rpc.ErrorInfo", reason: Optional("API_KEY_INVALID"), domain: Optional("googleapis.com"), metadata: Optional(["service": "firebasevertexai.googleapis.com"])), FirebaseAI.ErrorDetails(type: "type.googleapis.com/google.rpc.LocalizedMessage", reason: nil, domain: nil, metadata: nil)])"
}

return try parseResponse(T.Response.self, from: data)
Expand Down
95 changes: 95 additions & 0 deletions FirebaseAI/Sources/History.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@

// Copyright 2024 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

import Foundation

@available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *)
final class History: Sendable {
private let historyLock = NSLock()
private nonisolated(unsafe) var _history: [ModelContent] = []
/// The previous content from the chat that has been successfully sent and received from the
/// model. This will be provided to the model for each message sent as context for the discussion.
public var history: [ModelContent] {
get {
historyLock.withLock { _history }
}
set {
historyLock.withLock { _history = newValue }
}
}

init(history: [ModelContent]) {
self.history = history
}

func append(contentsOf: [ModelContent]) {
historyLock.withLock {
_history.append(contentsOf: contentsOf)
}
}

func append(_ newElement: ModelContent) {
historyLock.withLock {
_history.append(newElement)
}
}

func aggregatedChunks(_ chunks: [ModelContent]) -> ModelContent {
var parts: [InternalPart] = []
var combinedText = ""
var combinedThoughts = ""

func flush() {
if !combinedThoughts.isEmpty {
parts.append(InternalPart(.text(combinedThoughts), isThought: true, thoughtSignature: nil))
combinedThoughts = ""
}
if !combinedText.isEmpty {
parts.append(InternalPart(.text(combinedText), isThought: nil, thoughtSignature: nil))
combinedText = ""
}
}

// Loop through all the parts, aggregating the text.
for part in chunks.flatMap({ $0.internalParts }) {
// Only text parts may be combined.
if case let .text(text) = part.data, part.thoughtSignature == nil {
// Thought summaries must not be combined with regular text.
if part.isThought ?? false {
// If we were combining regular text, flush it before handling "thoughts".
if !combinedText.isEmpty {
flush()
}
combinedThoughts += text
} else {
// If we were combining "thoughts", flush it before handling regular text.
if !combinedThoughts.isEmpty {
flush()
}
combinedText += text
}
} else {
// This is a non-combinable part (not text), flush any pending text.
flush()
parts.append(part)
}
}

// Flush any remaining text.
flush()

return ModelContent(role: "model", parts: parts)
}
}
20 changes: 20 additions & 0 deletions FirebaseAI/Sources/ImageAPIMethod.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@

// Copyright 2025 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may not use this file except in compliance with the License.
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

import Foundation

enum ImageAPIMethod: String {
case generateImages = "templatePredict"
}
Loading
Loading