From e290ab4407174885e0722d68a3b31e2b9638271c Mon Sep 17 00:00:00 2001 From: Andrew Heard Date: Mon, 5 Feb 2024 17:29:57 -0500 Subject: [PATCH 001/124] Add prototype for Firebase Vertex AI --- FirebaseCore/Sources/FIRApp.m | 1 + FirebaseVertexAI/Sources/VertexAI.swift | 154 ++++++++++++++++++ .../Sources/VertexAIComponent.swift | 80 +++++++++ Package.swift | 21 +++ 4 files changed, 256 insertions(+) create mode 100644 FirebaseVertexAI/Sources/VertexAI.swift create mode 100644 FirebaseVertexAI/Sources/VertexAIComponent.swift diff --git a/FirebaseCore/Sources/FIRApp.m b/FirebaseCore/Sources/FIRApp.m index 8f4aefe4bc7..a8cc38c12de 100644 --- a/FirebaseCore/Sources/FIRApp.m +++ b/FirebaseCore/Sources/FIRApp.m @@ -829,6 +829,7 @@ + (void)registerSwiftComponents { @"FIRSessions" : @"fire-ses", @"FIRFunctionsComponent" : @"fire-fun", @"FIRStorageComponent" : @"fire-str", + @"FIRVertexAIComponent" : @"fire-vtx", }; for (NSString *className in swiftComponents.allKeys) { Class klass = NSClassFromString(className); diff --git a/FirebaseVertexAI/Sources/VertexAI.swift b/FirebaseVertexAI/Sources/VertexAI.swift new file mode 100644 index 00000000000..41aafaeee3d --- /dev/null +++ b/FirebaseVertexAI/Sources/VertexAI.swift @@ -0,0 +1,154 @@ +// 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 + +import FirebaseAppCheckInterop +import FirebaseCore +import GoogleGenerativeAI + +// Avoids exposing internal FirebaseCore APIs to Swift users. +@_implementationOnly import FirebaseCoreExtension + +@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, *) +@objc(FIRVertexAI) +open class VertexAI: NSObject { + // MARK: - Public APIs + + /// The default `VertexAI` instance. + /// + /// - Returns: An instance of `VertexAI`, configured with the default `FirebaseApp`. + public static func vertexAI(modelName: String, location: String) -> VertexAI { + return vertexAI(app: FirebaseApp.app()!, modelName: modelName, location: location) + } + + public static func vertexAI(app: FirebaseApp, modelName: String, location: String) -> VertexAI { + let provider = ComponentType.instance(for: VertexAIProvider.self, + in: app.container) + let modelResourceName = modelResourceName(app: app, modelName: modelName, location: location) + return provider.vertexAI(location: location, modelResourceName: modelResourceName) + } + + public func generateContentStream(prompt: String) async + -> AsyncThrowingStream { + return model.generateContentStream(prompt) + } + + // MARK: - Private + + /// The `FirebaseApp` associated with this `VertexAI` instance. + private let app: FirebaseApp + + private let appCheck: AppCheckInterop? + + private let location: String + + private let modelResouceName: String + + lazy var model: GenerativeModel = { + let options = RequestOptions(hooks: [ + setVertexAIEndpoint, + addAccessTokenHeader, + addAppCheckHeader, + ]) + return GenerativeModel( + name: modelResouceName, + apiKey: app.options.apiKey!, + requestOptions: options + ) + }() + + private static let accessTokenEnvKey = "FIRVertexAIAccessToken" + + init(app: FirebaseApp, location: String, modelResourceName: String) { + self.app = app + appCheck = ComponentType.instance(for: AppCheckInterop.self, in: app.container) + self.location = location + modelResouceName = modelResourceName + } + + private static func modelResourceName(app: FirebaseApp, modelName: String, + location: String) -> String { + if modelName.contains("/") { + return modelName + } + guard let projectID = app.options.projectID else { + print("The FirebaseApp is missing a project ID.") + return modelName + } + + return "projects/\(projectID)/locations/\(location)/publishers/google/models/\(modelName)" + } + + // MARK: Request Hooks + + /// Replace the Labs endpoint with a Vertex AI endpoint in the provided request. + /// + /// This is temporary workaround until the Google Generative AI SDK supports setting an endpoint. + /// + /// - Parameter request: The `URLRequest` to modify with a Vertex AI hostname. + func setVertexAIEndpoint(request: inout URLRequest) { + guard let requestURL = request.url else { + return + } + guard var urlComponents = URLComponents(url: requestURL, resolvingAgainstBaseURL: false) else { + return + } + urlComponents.host = "\(location)-aiplatform.googleapis.com" + + guard let componentsURL = urlComponents.url else { + return + } + + request.url = componentsURL + } + + /// Add a Google Cloud access token in an Authorization header in the provided request. + /// + /// This is a temporary workaround until Vertex AI can be called with an API key. + /// + /// - Parameter request: The `URLRequest` to modify by adding an access token. + func addAccessTokenHeader(request: inout URLRequest) { + // Remove the API key header, it is not supported by Vertex AI. + if var headers = request.allHTTPHeaderFields { + headers.removeValue(forKey: "x-goog-api-key") + } + + guard let accessToken = ProcessInfo.processInfo.environment[VertexAI.accessTokenEnvKey] else { + print(""" + Vertex AI requires an Access Token for authorization: + 1. Get an access token by running `gcloud auth print-access-token` + 2. Set it in the \(VertexAI.accessTokenEnvKey) environment variable. + """) + return + } + + request.addValue("Bearer \(accessToken)", forHTTPHeaderField: "Authorization") + } + + /// Adds an App Check token to the provided request, if App Check is included in the app. + /// + /// This demonstrates how an App Check token can be added to requests; it is currently ignored by + /// the backend. + /// + /// - Parameter request: The `URLRequest` to modify by adding an App Check token header. + func addAppCheckHeader(request: inout URLRequest) async { + guard let appCheck = appCheck else { + return + } + + let tokenResult = await appCheck.getToken(forcingRefresh: false) + request.addValue(tokenResult.token, forHTTPHeaderField: "X-Firebase-AppCheck") + } +} diff --git a/FirebaseVertexAI/Sources/VertexAIComponent.swift b/FirebaseVertexAI/Sources/VertexAIComponent.swift new file mode 100644 index 00000000000..a8d6c177c74 --- /dev/null +++ b/FirebaseVertexAI/Sources/VertexAIComponent.swift @@ -0,0 +1,80 @@ +// 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 FirebaseAppCheckInterop +import FirebaseCore +import Foundation + +// Avoids exposing internal FirebaseCore APIs to Swift users. +@_implementationOnly import FirebaseCoreExtension + +@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, *) +@objc(FIRVertexAIProvider) +protocol VertexAIProvider { + @objc func vertexAI(location: String, modelResourceName: String) -> VertexAI +} + +@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, *) +@objc(FIRVertexAIComponent) +class VertexAIComponent: NSObject, Library, VertexAIProvider { + // MARK: - Private Variables + + /// The app associated with all `VertexAI` instances in this container. + /// This is `unowned` instead of `weak` so it can be used without unwrapping in `vertexAI(...)` + private unowned let app: FirebaseApp + + /// A map of active `VertexAI` instances for `app`, keyed by model resource names + /// (e.g., "projects/my-project-id/locations/us-central1/publishers/google/models/gemini-pro"). + private var instances: [String: VertexAI] = [:] + + /// Lock to manage access to the `instances` array to avoid race conditions. + private var instancesLock: os_unfair_lock = .init() + + // MARK: - Initializers + + required init(app: FirebaseApp) { + self.app = app + } + + // MARK: - Library conformance + + static func componentsToRegister() -> [Component] { + let appCheckInterop = Dependency(with: AppCheckInterop.self, isRequired: false) + return [Component(VertexAIProvider.self, + instantiationTiming: .lazy, + dependencies: [ + appCheckInterop, + ]) { container, isCacheable in + guard let app = container.app else { return nil } + isCacheable.pointee = true + return self.init(app: app) + }] + } + + // MARK: - VertexAIProvider conformance + + func vertexAI(location: String, modelResourceName: String) -> VertexAI { + os_unfair_lock_lock(&instancesLock) + + // Unlock before the function returns. + defer { os_unfair_lock_unlock(&instancesLock) } + + if let instance = instances[modelResourceName] { + return instance + } + let newInstance = VertexAI(app: app, location: location, modelResourceName: modelResourceName) + instances[modelResourceName] = newInstance + return newInstance + } +} diff --git a/Package.swift b/Package.swift index f78597e8408..97f52f6fc79 100644 --- a/Package.swift +++ b/Package.swift @@ -137,6 +137,10 @@ let package = Package( name: "FirebaseStorage", targets: ["FirebaseStorage"] ), + .library( + name: "FirebaseVertexAI", + targets: ["FirebaseVertexAI"] + ), ], dependencies: [ .package( @@ -183,6 +187,10 @@ let package = Package( "100.0.0" ..< "101.0.0" ), .package(url: "https://github.com/google/app-check.git", "10.18.0" ..< "11.0.0"), + .package( + url: "https://github.com/google/generative-ai-swift.git", + revision: "f59b91c7df6ae4d48ff85ab58e640b5084712fa6" + ), ], targets: [ .target( @@ -1347,6 +1355,19 @@ let package = Package( .headerSearchPath("../../.."), ] ), + + // MARK: - Firebase Vertex AI + + .target( + name: "FirebaseVertexAI", + dependencies: [ + "FirebaseAppCheckInterop", + "FirebaseCore", + "FirebaseCoreExtension", + .product(name: "GoogleGenerativeAI", package: "generative-ai-swift"), + ], + path: "FirebaseVertexAI/Sources" + ), ] + firestoreTargets(), cLanguageStandard: .c99, cxxLanguageStandard: CXXLanguageStandard.gnucxx14 From da7e247b5a9e4f976970542ae18330b8ed8ad6ad Mon Sep 17 00:00:00 2001 From: Andrew Heard Date: Mon, 5 Feb 2024 17:53:44 -0500 Subject: [PATCH 002/124] Update generative-ai-swift dependency --- Package.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Package.swift b/Package.swift index 97f52f6fc79..125aa4a46bd 100644 --- a/Package.swift +++ b/Package.swift @@ -189,7 +189,7 @@ let package = Package( .package(url: "https://github.com/google/app-check.git", "10.18.0" ..< "11.0.0"), .package( url: "https://github.com/google/generative-ai-swift.git", - revision: "f59b91c7df6ae4d48ff85ab58e640b5084712fa6" + revision: "efcee26db89d1948ebd581b3097c94186d615282" ), ], targets: [ From 0d8715192448058f28b38eb73a5e134e720dd552 Mon Sep 17 00:00:00 2001 From: Andrew Heard Date: Wed, 14 Feb 2024 16:01:53 -0500 Subject: [PATCH 003/124] Use Swift 5.9 `AccessLevelOnImport` --- FirebaseVertexAI/Sources/VertexAI.swift | 29 ++++++++++++++----- .../Sources/VertexAIComponent.swift | 8 ++--- Package.swift | 9 ++++-- 3 files changed, 32 insertions(+), 14 deletions(-) diff --git a/FirebaseVertexAI/Sources/VertexAI.swift b/FirebaseVertexAI/Sources/VertexAI.swift index 41aafaeee3d..429ad02491a 100644 --- a/FirebaseVertexAI/Sources/VertexAI.swift +++ b/FirebaseVertexAI/Sources/VertexAI.swift @@ -14,12 +14,16 @@ import Foundation +public import FirebaseCore +public import class GoogleGenerativeAI.Chat +public import protocol GoogleGenerativeAI.PartsRepresentable +public import struct GoogleGenerativeAI.GenerateContentResponse +public import struct GoogleGenerativeAI.ModelContent + +import FirebaseCoreExtension import FirebaseAppCheckInterop -import FirebaseCore -import GoogleGenerativeAI -// Avoids exposing internal FirebaseCore APIs to Swift users. -@_implementationOnly import FirebaseCoreExtension +private import GoogleGenerativeAI @available(iOS 15.0, macOS 11.0, macCatalyst 15.0, *) @objc(FIRVertexAI) @@ -40,9 +44,20 @@ open class VertexAI: NSObject { return provider.vertexAI(location: location, modelResourceName: modelResourceName) } - public func generateContentStream(prompt: String) async - -> AsyncThrowingStream { - return model.generateContentStream(prompt) + public func generateContentStream(_ parts: GoogleGenerativeAI + .PartsRepresentable...) + -> AsyncThrowingStream { + return model.generateContentStream([GoogleGenerativeAI.ModelContent(parts: parts)]) + } + + public func generateContentStream(_ content: [GoogleGenerativeAI.ModelContent]) + -> AsyncThrowingStream { + return model.generateContentStream(content) + } + + public func startChat(history: [GoogleGenerativeAI.ModelContent] = []) -> GoogleGenerativeAI + .Chat { + return model.startChat(history: history) } // MARK: - Private diff --git a/FirebaseVertexAI/Sources/VertexAIComponent.swift b/FirebaseVertexAI/Sources/VertexAIComponent.swift index a8d6c177c74..68cc0220849 100644 --- a/FirebaseVertexAI/Sources/VertexAIComponent.swift +++ b/FirebaseVertexAI/Sources/VertexAIComponent.swift @@ -12,12 +12,12 @@ // See the License for the specific language governing permissions and // limitations under the License. -import FirebaseAppCheckInterop -import FirebaseCore import Foundation -// Avoids exposing internal FirebaseCore APIs to Swift users. -@_implementationOnly import FirebaseCoreExtension +public import FirebaseCore + +import FirebaseAppCheckInterop +import FirebaseCoreExtension @available(iOS 15.0, macOS 11.0, macCatalyst 15.0, *) @objc(FIRVertexAIProvider) diff --git a/Package.swift b/Package.swift index 125aa4a46bd..28e98373c3e 100644 --- a/Package.swift +++ b/Package.swift @@ -1,4 +1,4 @@ -// swift-tools-version:5.7.1 +// swift-tools-version:5.9 // The swift-tools-version declares the minimum version of Swift required to // build this package. @@ -189,7 +189,7 @@ let package = Package( .package(url: "https://github.com/google/app-check.git", "10.18.0" ..< "11.0.0"), .package( url: "https://github.com/google/generative-ai-swift.git", - revision: "efcee26db89d1948ebd581b3097c94186d615282" + revision: "4fe215743fd02c8182efdcfff5e7571873d2c158" ), ], targets: [ @@ -1366,7 +1366,10 @@ let package = Package( "FirebaseCoreExtension", .product(name: "GoogleGenerativeAI", package: "generative-ai-swift"), ], - path: "FirebaseVertexAI/Sources" + path: "FirebaseVertexAI/Sources", + swiftSettings: [ + .enableExperimentalFeature("AccessLevelOnImport"), + ] ), ] + firestoreTargets(), cLanguageStandard: .c99, From 71ac32686a73a6f03161d885bbd988875a08800e Mon Sep 17 00:00:00 2001 From: Andrew Heard Date: Tue, 20 Feb 2024 10:33:11 -0500 Subject: [PATCH 004/124] Set Vertex AI endpoint in `RequestOptions` --- FirebaseVertexAI/Sources/VertexAI.swift | 35 ++++++------------------- Package.swift | 2 +- 2 files changed, 9 insertions(+), 28 deletions(-) diff --git a/FirebaseVertexAI/Sources/VertexAI.swift b/FirebaseVertexAI/Sources/VertexAI.swift index 429ad02491a..e0478ea3a33 100644 --- a/FirebaseVertexAI/Sources/VertexAI.swift +++ b/FirebaseVertexAI/Sources/VertexAI.swift @@ -20,8 +20,8 @@ public import protocol GoogleGenerativeAI.PartsRepresentable public import struct GoogleGenerativeAI.GenerateContentResponse public import struct GoogleGenerativeAI.ModelContent -import FirebaseCoreExtension import FirebaseAppCheckInterop +import FirebaseCoreExtension private import GoogleGenerativeAI @@ -72,11 +72,13 @@ open class VertexAI: NSObject { private let modelResouceName: String lazy var model: GenerativeModel = { - let options = RequestOptions(hooks: [ - setVertexAIEndpoint, - addAccessTokenHeader, - addAppCheckHeader, - ]) + let options = RequestOptions( + endpoint: "\(location)-aiplatform.googleapis.com", + hooks: [ + addAccessTokenHeader, + addAppCheckHeader, + ] + ) return GenerativeModel( name: modelResouceName, apiKey: app.options.apiKey!, @@ -108,27 +110,6 @@ open class VertexAI: NSObject { // MARK: Request Hooks - /// Replace the Labs endpoint with a Vertex AI endpoint in the provided request. - /// - /// This is temporary workaround until the Google Generative AI SDK supports setting an endpoint. - /// - /// - Parameter request: The `URLRequest` to modify with a Vertex AI hostname. - func setVertexAIEndpoint(request: inout URLRequest) { - guard let requestURL = request.url else { - return - } - guard var urlComponents = URLComponents(url: requestURL, resolvingAgainstBaseURL: false) else { - return - } - urlComponents.host = "\(location)-aiplatform.googleapis.com" - - guard let componentsURL = urlComponents.url else { - return - } - - request.url = componentsURL - } - /// Add a Google Cloud access token in an Authorization header in the provided request. /// /// This is a temporary workaround until Vertex AI can be called with an API key. diff --git a/Package.swift b/Package.swift index 28e98373c3e..951dd278cea 100644 --- a/Package.swift +++ b/Package.swift @@ -189,7 +189,7 @@ let package = Package( .package(url: "https://github.com/google/app-check.git", "10.18.0" ..< "11.0.0"), .package( url: "https://github.com/google/generative-ai-swift.git", - revision: "4fe215743fd02c8182efdcfff5e7571873d2c158" + revision: "46e56ae138f152528b7b50520a22777f3d8adb18" ), ], targets: [ From 0f0d35ad6d98e0688ece21f3e312d236f7ff9e60 Mon Sep 17 00:00:00 2001 From: Andrew Heard Date: Tue, 20 Feb 2024 10:49:58 -0500 Subject: [PATCH 005/124] Revert "Use Swift 5.9 `AccessLevelOnImport`" This reverts commit af7d7836bc5f4fc5576a06bc7af646f3b179a66a. # Conflicts: # FirebaseVertexAI/Sources/VertexAI.swift # Package.swift --- FirebaseVertexAI/Sources/VertexAI.swift | 12 ++++-------- FirebaseVertexAI/Sources/VertexAIComponent.swift | 8 ++++---- Package.swift | 7 ++----- 3 files changed, 10 insertions(+), 17 deletions(-) diff --git a/FirebaseVertexAI/Sources/VertexAI.swift b/FirebaseVertexAI/Sources/VertexAI.swift index e0478ea3a33..9e3809c9828 100644 --- a/FirebaseVertexAI/Sources/VertexAI.swift +++ b/FirebaseVertexAI/Sources/VertexAI.swift @@ -14,16 +14,12 @@ import Foundation -public import FirebaseCore -public import class GoogleGenerativeAI.Chat -public import protocol GoogleGenerativeAI.PartsRepresentable -public import struct GoogleGenerativeAI.GenerateContentResponse -public import struct GoogleGenerativeAI.ModelContent - import FirebaseAppCheckInterop -import FirebaseCoreExtension +import FirebaseCore +import GoogleGenerativeAI -private import GoogleGenerativeAI +// Avoids exposing internal FirebaseCore APIs to Swift users. +@_implementationOnly import FirebaseCoreExtension @available(iOS 15.0, macOS 11.0, macCatalyst 15.0, *) @objc(FIRVertexAI) diff --git a/FirebaseVertexAI/Sources/VertexAIComponent.swift b/FirebaseVertexAI/Sources/VertexAIComponent.swift index 68cc0220849..a8d6c177c74 100644 --- a/FirebaseVertexAI/Sources/VertexAIComponent.swift +++ b/FirebaseVertexAI/Sources/VertexAIComponent.swift @@ -12,12 +12,12 @@ // See the License for the specific language governing permissions and // limitations under the License. +import FirebaseAppCheckInterop +import FirebaseCore import Foundation -public import FirebaseCore - -import FirebaseAppCheckInterop -import FirebaseCoreExtension +// Avoids exposing internal FirebaseCore APIs to Swift users. +@_implementationOnly import FirebaseCoreExtension @available(iOS 15.0, macOS 11.0, macCatalyst 15.0, *) @objc(FIRVertexAIProvider) diff --git a/Package.swift b/Package.swift index 951dd278cea..a491ecfacd2 100644 --- a/Package.swift +++ b/Package.swift @@ -1,4 +1,4 @@ -// swift-tools-version:5.9 +// swift-tools-version:5.7.1 // The swift-tools-version declares the minimum version of Swift required to // build this package. @@ -1366,10 +1366,7 @@ let package = Package( "FirebaseCoreExtension", .product(name: "GoogleGenerativeAI", package: "generative-ai-swift"), ], - path: "FirebaseVertexAI/Sources", - swiftSettings: [ - .enableExperimentalFeature("AccessLevelOnImport"), - ] + path: "FirebaseVertexAI/Sources" ), ] + firestoreTargets(), cLanguageStandard: .c99, From 7c5e5c38779eb12bd6a2e0cb8b84397455b64944 Mon Sep 17 00:00:00 2001 From: Andrew Heard Date: Tue, 20 Feb 2024 12:23:31 -0500 Subject: [PATCH 006/124] Add alternative API surface that returns `GenerativeModel` instances --- FirebaseVertexAI/Sources/VertexAI.swift | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/FirebaseVertexAI/Sources/VertexAI.swift b/FirebaseVertexAI/Sources/VertexAI.swift index 9e3809c9828..02fdb82807f 100644 --- a/FirebaseVertexAI/Sources/VertexAI.swift +++ b/FirebaseVertexAI/Sources/VertexAI.swift @@ -26,6 +26,27 @@ import GoogleGenerativeAI open class VertexAI: NSObject { // MARK: - Public APIs + // Option 1: Return `GoogleGenerativeAI.GenerativeModel` instances. + + /// Returns an instance of `GoogleGenerativeAI.GenerativeModel` that uses the Vertex AI API. + /// + /// This instance is configured with the default `FirebaseApp`. + public static func generativeModel(modelName: String, location: String) -> GoogleGenerativeAI + .GenerativeModel { + return vertexAI(modelName: modelName, location: location).model + } + + /// Returns an instance of `GoogleGenerativeAI.GenerativeModel` that uses the Vertex AI API. + public static func generativeModel(app: FirebaseApp, modelName: String, + location: String) -> GoogleGenerativeAI.GenerativeModel { + return vertexAI(app: FirebaseApp.app()!, modelName: modelName, location: location).model + } + + // Option 2: Return `VertexAI` instances with a similar API surface to + // `GoogleGenerativeAI.GenerativeModel`. + // Some types are re-used from the Google AI SDK for requests and responses, e.g., + // `GenerateContentResponse`. + /// The default `VertexAI` instance. /// /// - Returns: An instance of `VertexAI`, configured with the default `FirebaseApp`. From ee6651c766d00c001fcd77fb2b37645e6235a59f Mon Sep 17 00:00:00 2001 From: Andrew Heard Date: Thu, 22 Feb 2024 11:01:44 -0500 Subject: [PATCH 007/124] Add sample apps using Vertex AI --- .../AccentColor.colorset/Contents.json | 11 + .../AppIcon.appiconset/Contents.json | 13 + .../ChatSample/Assets.xcassets/Contents.json | 6 + .../Sample/ChatSample/ChatSampleApp.swift | 36 + .../ChatSample/Models/ChatMessage.swift | 64 + .../Preview Assets.xcassets/Contents.json | 6 + .../Screens/ConversationScreen.swift | 127 ++ .../ViewModels/ConversationViewModel.swift | 130 ++ .../ChatSample/Views/BouncingDots.swift | 77 ++ .../ChatSample/Views/ErrorDetailsView.swift | 247 ++++ .../Sample/ChatSample/Views/ErrorView.swift | 65 + .../Sample/ChatSample/Views/MessageView.swift | 108 ++ .../AccentColor.colorset/Contents.json | 11 + .../AppIcon.appiconset/Contents.json | 13 + .../Assets.xcassets/Contents.json | 6 + .../GenerativeAIMultimodalSampleApp.swift | 31 + .../Preview Assets.xcassets/Contents.json | 6 + .../Screens/PhotoReasoningScreen.swift | 65 + .../ViewModels/PhotoReasoningViewModel.swift | 119 ++ .../project.pbxproj | 1053 +++++++++++++++++ .../AccentColor.colorset/Contents.json | 11 + .../AppIcon.appiconset/Contents.json | 13 + .../Assets.xcassets/Contents.json | 6 + .../GenerativeAISample/ContentView.swift | 48 + .../GenerativeAISampleApp.swift | 29 + .../Preview Assets.xcassets/Contents.json | 6 + .../AccentColor.colorset/Contents.json | 11 + .../AppIcon.appiconset/Contents.json | 13 + .../Assets.xcassets/Contents.json | 6 + .../GenerativeAITextSampleApp.swift | 31 + .../Preview Assets.xcassets/Contents.json | 6 + .../Screens/SummarizeScreen.swift | 74 ++ .../ViewModels/SummarizeViewModel.swift | 68 ++ .../GenerativeAIUIComponents/Package.swift | 35 + .../GenerativeAIUIComponents/InputField.swift | 82 ++ .../MultimodalInputField.swift | 183 +++ FirebaseVertexAI/Sources/VertexAI.swift | 37 +- 37 files changed, 2822 insertions(+), 31 deletions(-) create mode 100644 FirebaseVertexAI/Sample/ChatSample/Assets.xcassets/AccentColor.colorset/Contents.json create mode 100644 FirebaseVertexAI/Sample/ChatSample/Assets.xcassets/AppIcon.appiconset/Contents.json create mode 100644 FirebaseVertexAI/Sample/ChatSample/Assets.xcassets/Contents.json create mode 100644 FirebaseVertexAI/Sample/ChatSample/ChatSampleApp.swift create mode 100644 FirebaseVertexAI/Sample/ChatSample/Models/ChatMessage.swift create mode 100644 FirebaseVertexAI/Sample/ChatSample/Preview Content/Preview Assets.xcassets/Contents.json create mode 100644 FirebaseVertexAI/Sample/ChatSample/Screens/ConversationScreen.swift create mode 100644 FirebaseVertexAI/Sample/ChatSample/ViewModels/ConversationViewModel.swift create mode 100644 FirebaseVertexAI/Sample/ChatSample/Views/BouncingDots.swift create mode 100644 FirebaseVertexAI/Sample/ChatSample/Views/ErrorDetailsView.swift create mode 100644 FirebaseVertexAI/Sample/ChatSample/Views/ErrorView.swift create mode 100644 FirebaseVertexAI/Sample/ChatSample/Views/MessageView.swift create mode 100644 FirebaseVertexAI/Sample/GenerativeAIMultimodalSample/Assets.xcassets/AccentColor.colorset/Contents.json create mode 100644 FirebaseVertexAI/Sample/GenerativeAIMultimodalSample/Assets.xcassets/AppIcon.appiconset/Contents.json create mode 100644 FirebaseVertexAI/Sample/GenerativeAIMultimodalSample/Assets.xcassets/Contents.json create mode 100644 FirebaseVertexAI/Sample/GenerativeAIMultimodalSample/GenerativeAIMultimodalSampleApp.swift create mode 100644 FirebaseVertexAI/Sample/GenerativeAIMultimodalSample/Preview Content/Preview Assets.xcassets/Contents.json create mode 100644 FirebaseVertexAI/Sample/GenerativeAIMultimodalSample/Screens/PhotoReasoningScreen.swift create mode 100644 FirebaseVertexAI/Sample/GenerativeAIMultimodalSample/ViewModels/PhotoReasoningViewModel.swift create mode 100644 FirebaseVertexAI/Sample/GenerativeAISample.xcodeproj/project.pbxproj create mode 100644 FirebaseVertexAI/Sample/GenerativeAISample/Assets.xcassets/AccentColor.colorset/Contents.json create mode 100644 FirebaseVertexAI/Sample/GenerativeAISample/Assets.xcassets/AppIcon.appiconset/Contents.json create mode 100644 FirebaseVertexAI/Sample/GenerativeAISample/Assets.xcassets/Contents.json create mode 100644 FirebaseVertexAI/Sample/GenerativeAISample/ContentView.swift create mode 100644 FirebaseVertexAI/Sample/GenerativeAISample/GenerativeAISampleApp.swift create mode 100644 FirebaseVertexAI/Sample/GenerativeAISample/Preview Content/Preview Assets.xcassets/Contents.json create mode 100644 FirebaseVertexAI/Sample/GenerativeAITextSample/Assets.xcassets/AccentColor.colorset/Contents.json create mode 100644 FirebaseVertexAI/Sample/GenerativeAITextSample/Assets.xcassets/AppIcon.appiconset/Contents.json create mode 100644 FirebaseVertexAI/Sample/GenerativeAITextSample/Assets.xcassets/Contents.json create mode 100644 FirebaseVertexAI/Sample/GenerativeAITextSample/GenerativeAITextSampleApp.swift create mode 100644 FirebaseVertexAI/Sample/GenerativeAITextSample/Preview Content/Preview Assets.xcassets/Contents.json create mode 100644 FirebaseVertexAI/Sample/GenerativeAITextSample/Screens/SummarizeScreen.swift create mode 100644 FirebaseVertexAI/Sample/GenerativeAITextSample/ViewModels/SummarizeViewModel.swift create mode 100644 FirebaseVertexAI/Sample/GenerativeAIUIComponents/Package.swift create mode 100644 FirebaseVertexAI/Sample/GenerativeAIUIComponents/Sources/GenerativeAIUIComponents/InputField.swift create mode 100644 FirebaseVertexAI/Sample/GenerativeAIUIComponents/Sources/GenerativeAIUIComponents/MultimodalInputField.swift diff --git a/FirebaseVertexAI/Sample/ChatSample/Assets.xcassets/AccentColor.colorset/Contents.json b/FirebaseVertexAI/Sample/ChatSample/Assets.xcassets/AccentColor.colorset/Contents.json new file mode 100644 index 00000000000..eb878970081 --- /dev/null +++ b/FirebaseVertexAI/Sample/ChatSample/Assets.xcassets/AccentColor.colorset/Contents.json @@ -0,0 +1,11 @@ +{ + "colors" : [ + { + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/FirebaseVertexAI/Sample/ChatSample/Assets.xcassets/AppIcon.appiconset/Contents.json b/FirebaseVertexAI/Sample/ChatSample/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 00000000000..13613e3ee1a --- /dev/null +++ b/FirebaseVertexAI/Sample/ChatSample/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,13 @@ +{ + "images" : [ + { + "idiom" : "universal", + "platform" : "ios", + "size" : "1024x1024" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/FirebaseVertexAI/Sample/ChatSample/Assets.xcassets/Contents.json b/FirebaseVertexAI/Sample/ChatSample/Assets.xcassets/Contents.json new file mode 100644 index 00000000000..73c00596a7f --- /dev/null +++ b/FirebaseVertexAI/Sample/ChatSample/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/FirebaseVertexAI/Sample/ChatSample/ChatSampleApp.swift b/FirebaseVertexAI/Sample/ChatSample/ChatSampleApp.swift new file mode 100644 index 00000000000..7d7e02d7e3c --- /dev/null +++ b/FirebaseVertexAI/Sample/ChatSample/ChatSampleApp.swift @@ -0,0 +1,36 @@ +// Copyright 2023 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 FirebaseCore +import FirebaseVertexAI +import SwiftUI + +@main +struct ChatSampleApp: App { + @StateObject + var viewModel = ConversationViewModel() + + init() { + FirebaseApp.configure() + } + + var body: some Scene { + WindowGroup { + NavigationStack { + ConversationScreen() + .environmentObject(viewModel) + } + } + } +} diff --git a/FirebaseVertexAI/Sample/ChatSample/Models/ChatMessage.swift b/FirebaseVertexAI/Sample/ChatSample/Models/ChatMessage.swift new file mode 100644 index 00000000000..6f7ab321b12 --- /dev/null +++ b/FirebaseVertexAI/Sample/ChatSample/Models/ChatMessage.swift @@ -0,0 +1,64 @@ +// Copyright 2023 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 Participant { + case system + case user +} + +struct ChatMessage: Identifiable, Equatable { + let id = UUID().uuidString + var message: String + let participant: Participant + var pending = false + + static func pending(participant: Participant) -> ChatMessage { + Self(message: "", participant: participant, pending: true) + } +} + +extension ChatMessage { + static var samples: [ChatMessage] = [ + .init(message: "Hello. What can I do for you today?", participant: .system), + .init(message: "Show me a simple loop in Swift.", participant: .user), + .init(message: """ + Sure, here is a simple loop in Swift: + + # Example 1 + ``` + for i in 1...5 { + print("Hello, world!") + } + ``` + + This loop will print the string "Hello, world!" five times. The for loop iterates over a range of numbers, + in this case the numbers from 1 to 5. The variable i is assigned each number in the range, and the code inside the loop is executed. + + **Here is another example of a simple loop in Swift:** + ```swift + var sum = 0 + for i in 1...100 { + sum += i + } + print("The sum of the numbers from 1 to 100 is \\(sum).") + ``` + + This loop calculates the sum of the numbers from 1 to 100. The variable sum is initialized to 0, and then the for loop iterates over the range of numbers from 1 to 100. The variable i is assigned each number in the range, and the value of i is added to the sum variable. After the loop has finished executing, the value of sum is printed to the console. + """, participant: .system), + ] + + static var sample = samples[0] +} diff --git a/FirebaseVertexAI/Sample/ChatSample/Preview Content/Preview Assets.xcassets/Contents.json b/FirebaseVertexAI/Sample/ChatSample/Preview Content/Preview Assets.xcassets/Contents.json new file mode 100644 index 00000000000..73c00596a7f --- /dev/null +++ b/FirebaseVertexAI/Sample/ChatSample/Preview Content/Preview Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/FirebaseVertexAI/Sample/ChatSample/Screens/ConversationScreen.swift b/FirebaseVertexAI/Sample/ChatSample/Screens/ConversationScreen.swift new file mode 100644 index 00000000000..a3044a1f430 --- /dev/null +++ b/FirebaseVertexAI/Sample/ChatSample/Screens/ConversationScreen.swift @@ -0,0 +1,127 @@ +// Copyright 2023 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 FirebaseVertexAI +import GenerativeAIUIComponents +import SwiftUI + +struct ConversationScreen: View { + @EnvironmentObject + var viewModel: ConversationViewModel + + @State + private var userPrompt = "" + + enum FocusedField: Hashable { + case message + } + + @FocusState + var focusedField: FocusedField? + + var body: some View { + VStack { + ScrollViewReader { scrollViewProxy in + List { + ForEach(viewModel.messages) { message in + MessageView(message: message) + } + if let error = viewModel.error { + ErrorView(error: error) + .tag("errorView") + } + } + .listStyle(.plain) + .onChange(of: viewModel.messages, perform: { newValue in + if viewModel.hasError { + // wait for a short moment to make sure we can actually scroll to the bottom + DispatchQueue.main.asyncAfter(deadline: .now() + 0.05) { + withAnimation { + scrollViewProxy.scrollTo("errorView", anchor: .bottom) + } + focusedField = .message + } + } else { + guard let lastMessage = viewModel.messages.last else { return } + + // wait for a short moment to make sure we can actually scroll to the bottom + DispatchQueue.main.asyncAfter(deadline: .now() + 0.05) { + withAnimation { + scrollViewProxy.scrollTo(lastMessage.id, anchor: .bottom) + } + focusedField = .message + } + } + }) + } + InputField("Message...", text: $userPrompt) { + Image(systemName: viewModel.busy ? "stop.circle.fill" : "arrow.up.circle.fill") + .font(.title) + } + .focused($focusedField, equals: .message) + .onSubmit { sendOrStop() } + } + .toolbar { + ToolbarItem(placement: .primaryAction) { + Button(action: newChat) { + Image(systemName: "square.and.pencil") + } + } + } + .navigationTitle("Chat sample") + .onAppear { + focusedField = .message + } + } + + private func sendMessage() { + Task { + let prompt = userPrompt + userPrompt = "" + await viewModel.sendMessage(prompt, streaming: true) + } + } + + private func sendOrStop() { + if viewModel.busy { + viewModel.stop() + } else { + sendMessage() + } + } + + private func newChat() { + viewModel.startNewChat() + } +} + +struct ConversationScreen_Previews: PreviewProvider { + struct ContainerView: View { + @StateObject var viewModel = ConversationViewModel() + + var body: some View { + ConversationScreen() + .environmentObject(viewModel) + .onAppear { + viewModel.messages = ChatMessage.samples + } + } + } + + static var previews: some View { + NavigationStack { + ConversationScreen() + } + } +} diff --git a/FirebaseVertexAI/Sample/ChatSample/ViewModels/ConversationViewModel.swift b/FirebaseVertexAI/Sample/ChatSample/ViewModels/ConversationViewModel.swift new file mode 100644 index 00000000000..465789be387 --- /dev/null +++ b/FirebaseVertexAI/Sample/ChatSample/ViewModels/ConversationViewModel.swift @@ -0,0 +1,130 @@ +// Copyright 2023 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 FirebaseVertexAI +import Foundation +import UIKit + +@MainActor +class ConversationViewModel: ObservableObject { + /// This array holds both the user's and the system's chat messages + @Published var messages = [ChatMessage]() + + /// Indicates we're waiting for the model to finish + @Published var busy = false + + @Published var error: Error? + var hasError: Bool { + return error != nil + } + + private var model: GenerativeModel + private var chat: Chat + private var stopGenerating = false + + private var chatTask: Task? + + init() { + model = VertexAI.generativeModel(modelName: "gemini-1.0-pro", location: "us-central1") + chat = model.startChat() + } + + func sendMessage(_ text: String, streaming: Bool = true) async { + error = nil + if streaming { + await internalSendMessageStreaming(text) + } else { + await internalSendMessage(text) + } + } + + func startNewChat() { + stop() + error = nil + chat = model.startChat() + messages.removeAll() + } + + func stop() { + chatTask?.cancel() + error = nil + } + + private func internalSendMessageStreaming(_ text: String) async { + chatTask?.cancel() + + chatTask = Task { + busy = true + defer { + busy = false + } + + // first, add the user's message to the chat + let userMessage = ChatMessage(message: text, participant: .user) + messages.append(userMessage) + + // add a pending message while we're waiting for a response from the backend + let systemMessage = ChatMessage.pending(participant: .system) + messages.append(systemMessage) + + do { + let responseStream = chat.sendMessageStream(text) + for try await chunk in responseStream { + messages[messages.count - 1].pending = false + if let text = chunk.text { + messages[messages.count - 1].message += text + } + } + } catch { + self.error = error + print(error.localizedDescription) + messages.removeLast() + } + } + } + + private func internalSendMessage(_ text: String) async { + chatTask?.cancel() + + chatTask = Task { + busy = true + defer { + busy = false + } + + // first, add the user's message to the chat + let userMessage = ChatMessage(message: text, participant: .user) + messages.append(userMessage) + + // add a pending message while we're waiting for a response from the backend + let systemMessage = ChatMessage.pending(participant: .system) + messages.append(systemMessage) + + do { + var response: GenerateContentResponse? + response = try await chat.sendMessage(text) + + if let responseText = response?.text { + // replace pending message with backend response + messages[messages.count - 1].message = responseText + messages[messages.count - 1].pending = false + } + } catch { + self.error = error + print(error.localizedDescription) + messages.removeLast() + } + } + } +} diff --git a/FirebaseVertexAI/Sample/ChatSample/Views/BouncingDots.swift b/FirebaseVertexAI/Sample/ChatSample/Views/BouncingDots.swift new file mode 100644 index 00000000000..6895e6723da --- /dev/null +++ b/FirebaseVertexAI/Sample/ChatSample/Views/BouncingDots.swift @@ -0,0 +1,77 @@ +// Copyright 2023 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 SwiftUI + +struct BouncingDots: View { + @State + private var dot1YOffset: CGFloat = 0.0 + + @State + private var dot2YOffset: CGFloat = 0.0 + + @State + private var dot3YOffset: CGFloat = 0.0 + + let animation = Animation.easeInOut(duration: 0.8) + .repeatForever(autoreverses: true) + + var body: some View { + HStack(spacing: 8) { + Circle() + .fill(Color.white) + .frame(width: 10, height: 10) + .offset(y: dot1YOffset) + .onAppear { + withAnimation(self.animation.delay(0.0)) { + self.dot1YOffset = -5 + } + } + Circle() + .fill(Color.white) + .frame(width: 10, height: 10) + .offset(y: dot2YOffset) + .onAppear { + withAnimation(self.animation.delay(0.2)) { + self.dot2YOffset = -5 + } + } + Circle() + .fill(Color.white) + .frame(width: 10, height: 10) + .offset(y: dot3YOffset) + .onAppear { + withAnimation(self.animation.delay(0.4)) { + self.dot3YOffset = -5 + } + } + } + .onAppear { + let baseOffset: CGFloat = -2 + + self.dot1YOffset = baseOffset + self.dot2YOffset = baseOffset + self.dot3YOffset = baseOffset + } + } +} + +struct BouncingDots_Previews: PreviewProvider { + static var previews: some View { + BouncingDots() + .frame(width: 200, height: 50) + .background(.blue) + .roundedCorner(10, corners: [.allCorners]) + } +} diff --git a/FirebaseVertexAI/Sample/ChatSample/Views/ErrorDetailsView.swift b/FirebaseVertexAI/Sample/ChatSample/Views/ErrorDetailsView.swift new file mode 100644 index 00000000000..e601876b2d9 --- /dev/null +++ b/FirebaseVertexAI/Sample/ChatSample/Views/ErrorDetailsView.swift @@ -0,0 +1,247 @@ +// Copyright 2023 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 FirebaseVertexAI +import MarkdownUI +import SwiftUI + +extension SafetySetting.HarmCategory: CustomStringConvertible { + public var description: String { + switch self { + case .dangerousContent: "Dangerous content" + case .harassment: "Harassment" + case .hateSpeech: "Hate speech" + case .sexuallyExplicit: "Sexually explicit" + case .unknown: "Unknown" + case .unspecified: "Unspecified" + } + } +} + +extension SafetyRating.HarmProbability: CustomStringConvertible { + public var description: String { + switch self { + case .high: "High" + case .low: "Low" + case .medium: "Medium" + case .negligible: "Negligible" + case .unknown: "Unknown" + case .unspecified: "Unspecified" + } + } +} + +private struct SubtitleFormRow: View { + var title: String + var value: String + + var body: some View { + VStack(alignment: .leading) { + Text(title) + .font(.subheadline) + Text(value) + } + } +} + +private struct SubtitleMarkdownFormRow: View { + var title: String + var value: String + + var body: some View { + VStack(alignment: .leading) { + Text(title) + .font(.subheadline) + Markdown(value) + } + } +} + +private struct SafetyRatingsSection: View { + var ratings: [SafetyRating] + + var body: some View { + Section("Safety ratings") { + List(ratings, id: \.self) { rating in + HStack { + Text("\(String(describing: rating.category))") + .font(.subheadline) + Spacer() + Text("\(String(describing: rating.probability))") + } + } + } + } +} + +struct ErrorDetailsView: View { + var error: Error + + var body: some View { + NavigationView { + Form { + switch error { + case let GenerateContentError.internalError(underlying: underlyingError): + Section("Error Type") { + Text("Internal error") + } + + Section("Details") { + SubtitleFormRow(title: "Error description", + value: underlyingError.localizedDescription) + } + + case let GenerateContentError.promptBlocked(response: generateContentResponse): + Section("Error Type") { + Text("Your prompt was blocked") + } + + Section("Details") { + if let reason = generateContentResponse.promptFeedback?.blockReason { + SubtitleFormRow(title: "Reason for blocking", value: reason.rawValue) + } + + if let text = generateContentResponse.text { + SubtitleMarkdownFormRow(title: "Last chunk for the response", value: text) + } + } + + if let ratings = generateContentResponse.candidates.first?.safetyRatings { + SafetyRatingsSection(ratings: ratings) + } + + case let GenerateContentError.responseStoppedEarly( + reason: finishReason, + response: generateContentResponse + ): + + Section("Error Type") { + Text("Response stopped early") + } + + Section("Details") { + SubtitleFormRow(title: "Reason for finishing early", value: finishReason.rawValue) + + if let text = generateContentResponse.text { + SubtitleMarkdownFormRow(title: "Last chunk for the response", value: text) + } + } + + if let ratings = generateContentResponse.candidates.first?.safetyRatings { + SafetyRatingsSection(ratings: ratings) + } + + case GenerateContentError.invalidAPIKey: + Section("Error Type") { + Text("Invalid API Key") + } + + Section("Details") { + SubtitleFormRow(title: "Error description", value: error.localizedDescription) + SubtitleMarkdownFormRow( + title: "Help", + value: """ + Please provide a valid value for `API_KEY` in the `GenerativeAI-Info.plist` file. + """ + ) + } + + case GenerateContentError.unsupportedUserLocation: + Section("Error Type") { + Text("Unsupported User Location") + } + + Section("Details") { + SubtitleFormRow(title: "Error description", value: error.localizedDescription) + SubtitleMarkdownFormRow( + title: "Help", + value: """ + The API is unsupported in your location (country / territory); please see the list of + [available regions](https://ai.google.dev/available_regions#available_regions). + """ + ) + } + + default: + Section("Error Type") { + Text("Some other error") + } + + Section("Details") { + SubtitleFormRow(title: "Error description", value: error.localizedDescription) + } + } + } + .navigationTitle("Error details") + .navigationBarTitleDisplayMode(.inline) + } + } +} + +#Preview("Response Stopped Early") { + let error = GenerateContentError.responseStoppedEarly( + reason: .maxTokens, + response: GenerateContentResponse(candidates: [ + CandidateResponse(content: ModelContent(role: "model", [ + """ + A _hypothetical_ model response. + Cillum ex aliqua amet aliquip labore amet eiusmod consectetur reprehenderit sit commodo. + """, + ]), + safetyRatings: [ + SafetyRating(category: .dangerousContent, probability: .high), + SafetyRating(category: .harassment, probability: .low), + SafetyRating(category: .hateSpeech, probability: .low), + SafetyRating(category: .sexuallyExplicit, probability: .low), + ], + finishReason: FinishReason.maxTokens, + citationMetadata: nil), + ], + promptFeedback: nil) + ) + + return ErrorDetailsView(error: error) +} + +#Preview("Prompt Blocked") { + let error = GenerateContentError.promptBlocked( + response: GenerateContentResponse(candidates: [ + CandidateResponse(content: ModelContent(role: "model", [ + """ + A _hypothetical_ model response. + Cillum ex aliqua amet aliquip labore amet eiusmod consectetur reprehenderit sit commodo. + """, + ]), + safetyRatings: [ + SafetyRating(category: .dangerousContent, probability: .high), + SafetyRating(category: .harassment, probability: .low), + SafetyRating(category: .hateSpeech, probability: .low), + SafetyRating(category: .sexuallyExplicit, probability: .low), + ], + finishReason: FinishReason.other, + citationMetadata: nil), + ], + promptFeedback: nil) + ) + + return ErrorDetailsView(error: error) +} + +#Preview("Invalid API Key") { + ErrorDetailsView(error: GenerateContentError.invalidAPIKey) +} + +#Preview("Unsupported User Location") { + ErrorDetailsView(error: GenerateContentError.unsupportedUserLocation) +} diff --git a/FirebaseVertexAI/Sample/ChatSample/Views/ErrorView.swift b/FirebaseVertexAI/Sample/ChatSample/Views/ErrorView.swift new file mode 100644 index 00000000000..1307eee62d4 --- /dev/null +++ b/FirebaseVertexAI/Sample/ChatSample/Views/ErrorView.swift @@ -0,0 +1,65 @@ +// Copyright 2023 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 FirebaseVertexAI +import SwiftUI + +struct ErrorView: View { + var error: Error + @State private var isDetailsSheetPresented = false + var body: some View { + HStack { + Text("An error occurred.") + Button(action: { isDetailsSheetPresented.toggle() }) { + Image(systemName: "info.circle") + } + } + .frame(maxWidth: .infinity, alignment: .center) + .listRowSeparator(.hidden) + .sheet(isPresented: $isDetailsSheetPresented) { + ErrorDetailsView(error: error) + } + } +} + +#Preview { + NavigationView { + let errorPromptBlocked = GenerateContentError.promptBlocked( + response: GenerateContentResponse(candidates: [ + CandidateResponse(content: ModelContent(role: "model", [ + """ + A _hypothetical_ model response. + Cillum ex aliqua amet aliquip labore amet eiusmod consectetur reprehenderit sit commodo. + """, + ]), + safetyRatings: [ + SafetyRating(category: .dangerousContent, probability: .high), + SafetyRating(category: .harassment, probability: .low), + SafetyRating(category: .hateSpeech, probability: .low), + SafetyRating(category: .sexuallyExplicit, probability: .low), + ], + finishReason: FinishReason.other, + citationMetadata: nil), + ], + promptFeedback: nil) + ) + List { + MessageView(message: ChatMessage.samples[0]) + MessageView(message: ChatMessage.samples[1]) + ErrorView(error: errorPromptBlocked) + } + .listStyle(.plain) + .navigationTitle("Chat sample") + } +} diff --git a/FirebaseVertexAI/Sample/ChatSample/Views/MessageView.swift b/FirebaseVertexAI/Sample/ChatSample/Views/MessageView.swift new file mode 100644 index 00000000000..79894503ffd --- /dev/null +++ b/FirebaseVertexAI/Sample/ChatSample/Views/MessageView.swift @@ -0,0 +1,108 @@ +// Copyright 2023 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 MarkdownUI +import SwiftUI + +struct RoundedCorner: Shape { + var radius: CGFloat = .infinity + var corners: UIRectCorner = .allCorners + + func path(in rect: CGRect) -> Path { + let path = UIBezierPath( + roundedRect: rect, + byRoundingCorners: corners, + cornerRadii: CGSize(width: radius, height: radius) + ) + return Path(path.cgPath) + } +} + +extension View { + func roundedCorner(_ radius: CGFloat, corners: UIRectCorner) -> some View { + clipShape(RoundedCorner(radius: radius, corners: corners)) + } +} + +struct MessageContentView: View { + var message: ChatMessage + + var body: some View { + if message.pending { + BouncingDots() + } else { + Markdown(message.message) + .markdownTextStyle { + FontFamilyVariant(.normal) + FontSize(.em(0.85)) + ForegroundColor(message.participant == .system ? Color(UIColor.label) : .white) + } + .markdownBlockStyle(\.codeBlock) { configuration in + configuration.label + .relativeLineSpacing(.em(0.25)) + .markdownTextStyle { + FontFamilyVariant(.monospaced) + FontSize(.em(0.85)) + ForegroundColor(Color(.label)) + } + .padding() + .background(Color(.secondarySystemBackground)) + .clipShape(RoundedRectangle(cornerRadius: 8)) + .markdownMargin(top: .zero, bottom: .em(0.8)) + } + } + } +} + +struct MessageView: View { + var message: ChatMessage + + var body: some View { + HStack { + if message.participant == .user { + Spacer() + } + MessageContentView(message: message) + .padding(10) + .background(message.participant == .system + ? Color(UIColor.systemFill) + : Color(UIColor.systemBlue)) + .roundedCorner(10, + corners: [ + .topLeft, + .topRight, + message.participant == .system ? .bottomRight : .bottomLeft, + ]) + if message.participant == .system { + Spacer() + } + } + .listRowSeparator(.hidden) + } +} + +struct MessageView_Previews: PreviewProvider { + static var previews: some View { + NavigationView { + List { + MessageView(message: ChatMessage.samples[0]) + MessageView(message: ChatMessage.samples[1]) + MessageView(message: ChatMessage.samples[2]) + MessageView(message: ChatMessage(message: "Hello!", participant: .system, pending: true)) + } + .listStyle(.plain) + .navigationTitle("Chat sample") + } + } +} diff --git a/FirebaseVertexAI/Sample/GenerativeAIMultimodalSample/Assets.xcassets/AccentColor.colorset/Contents.json b/FirebaseVertexAI/Sample/GenerativeAIMultimodalSample/Assets.xcassets/AccentColor.colorset/Contents.json new file mode 100644 index 00000000000..eb878970081 --- /dev/null +++ b/FirebaseVertexAI/Sample/GenerativeAIMultimodalSample/Assets.xcassets/AccentColor.colorset/Contents.json @@ -0,0 +1,11 @@ +{ + "colors" : [ + { + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/FirebaseVertexAI/Sample/GenerativeAIMultimodalSample/Assets.xcassets/AppIcon.appiconset/Contents.json b/FirebaseVertexAI/Sample/GenerativeAIMultimodalSample/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 00000000000..13613e3ee1a --- /dev/null +++ b/FirebaseVertexAI/Sample/GenerativeAIMultimodalSample/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,13 @@ +{ + "images" : [ + { + "idiom" : "universal", + "platform" : "ios", + "size" : "1024x1024" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/FirebaseVertexAI/Sample/GenerativeAIMultimodalSample/Assets.xcassets/Contents.json b/FirebaseVertexAI/Sample/GenerativeAIMultimodalSample/Assets.xcassets/Contents.json new file mode 100644 index 00000000000..73c00596a7f --- /dev/null +++ b/FirebaseVertexAI/Sample/GenerativeAIMultimodalSample/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/FirebaseVertexAI/Sample/GenerativeAIMultimodalSample/GenerativeAIMultimodalSampleApp.swift b/FirebaseVertexAI/Sample/GenerativeAIMultimodalSample/GenerativeAIMultimodalSampleApp.swift new file mode 100644 index 00000000000..49565de52d1 --- /dev/null +++ b/FirebaseVertexAI/Sample/GenerativeAIMultimodalSample/GenerativeAIMultimodalSampleApp.swift @@ -0,0 +1,31 @@ +// Copyright 2023 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 FirebaseCore +import SwiftUI + +@main +struct GenerativeAIMultimodalSampleApp: App { + init() { + FirebaseApp.configure() + } + + var body: some Scene { + WindowGroup { + NavigationStack { + PhotoReasoningScreen() + } + } + } +} diff --git a/FirebaseVertexAI/Sample/GenerativeAIMultimodalSample/Preview Content/Preview Assets.xcassets/Contents.json b/FirebaseVertexAI/Sample/GenerativeAIMultimodalSample/Preview Content/Preview Assets.xcassets/Contents.json new file mode 100644 index 00000000000..73c00596a7f --- /dev/null +++ b/FirebaseVertexAI/Sample/GenerativeAIMultimodalSample/Preview Content/Preview Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/FirebaseVertexAI/Sample/GenerativeAIMultimodalSample/Screens/PhotoReasoningScreen.swift b/FirebaseVertexAI/Sample/GenerativeAIMultimodalSample/Screens/PhotoReasoningScreen.swift new file mode 100644 index 00000000000..98f327585db --- /dev/null +++ b/FirebaseVertexAI/Sample/GenerativeAIMultimodalSample/Screens/PhotoReasoningScreen.swift @@ -0,0 +1,65 @@ +// Copyright 2023 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 GenerativeAIUIComponents +import MarkdownUI +import PhotosUI +import SwiftUI + +struct PhotoReasoningScreen: View { + @StateObject var viewModel = PhotoReasoningViewModel() + + var body: some View { + VStack { + MultimodalInputField(text: $viewModel.userInput, selection: $viewModel.selectedItems) + .onSubmit { + onSendTapped() + } + + ScrollViewReader { scrollViewProxy in + List { + if let outputText = viewModel.outputText { + HStack(alignment: .top) { + if viewModel.inProgress { + ProgressView() + } else { + Image(systemName: "cloud.circle.fill") + .font(.title2) + } + + Markdown("\(outputText)") + } + .listRowSeparator(.hidden) + } + } + .listStyle(.plain) + } + } + .navigationTitle("Multimodal sample") + } + + // MARK: - Actions + + private func onSendTapped() { + Task { + await viewModel.reason() + } + } +} + +#Preview { + NavigationStack { + PhotoReasoningScreen() + } +} diff --git a/FirebaseVertexAI/Sample/GenerativeAIMultimodalSample/ViewModels/PhotoReasoningViewModel.swift b/FirebaseVertexAI/Sample/GenerativeAIMultimodalSample/ViewModels/PhotoReasoningViewModel.swift new file mode 100644 index 00000000000..aa646c127df --- /dev/null +++ b/FirebaseVertexAI/Sample/GenerativeAIMultimodalSample/ViewModels/PhotoReasoningViewModel.swift @@ -0,0 +1,119 @@ +// Copyright 2023 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 FirebaseVertexAI +import Foundation +import OSLog +import PhotosUI +import SwiftUI + +@MainActor +class PhotoReasoningViewModel: ObservableObject { + // Maximum value for the larger of the two image dimensions (height and width) in pixels. This is + // being used to reduce the image size in bytes. + private static let largestImageDimension = 768.0 + + private var logger = Logger(subsystem: Bundle.main.bundleIdentifier!, category: "generative-ai") + + @Published + var userInput: String = "" + + @Published + var selectedItems = [PhotosPickerItem]() + + @Published + var outputText: String? = nil + + @Published + var errorMessage: String? + + @Published + var inProgress = false + + private var model: GenerativeModel? + + init() { + model = VertexAI.generativeModel(modelName: "gemini-1.0-pro-vision", location: "us-central1") + } + + func reason() async { + defer { + inProgress = false + } + guard let model else { + return + } + + do { + inProgress = true + errorMessage = nil + outputText = "" + + let prompt = "Look at the image(s), and then answer the following question: \(userInput)" + + var images = [PartsRepresentable]() + for item in selectedItems { + if let data = try? await item.loadTransferable(type: Data.self) { + guard let image = UIImage(data: data) else { + logger.error("Failed to parse data as an image, skipping.") + continue + } + if image.size.fits(largestDimension: PhotoReasoningViewModel.largestImageDimension) { + images.append(image) + } else { + guard let resizedImage = image + .preparingThumbnail(of: image.size + .aspectFit(largestDimension: PhotoReasoningViewModel.largestImageDimension)) else { + logger.error("Failed to resize image: \(image)") + continue + } + + images.append(resizedImage) + } + } + } + + let outputContentStream = model.generateContentStream(prompt, images) + + // stream response + for try await outputContent in outputContentStream { + guard let line = outputContent.text else { + return + } + + outputText = (outputText ?? "") + line + } + } catch { + logger.error("\(error.localizedDescription)") + errorMessage = error.localizedDescription + } + } +} + +private extension CGSize { + func fits(largestDimension length: CGFloat) -> Bool { + return width <= length && height <= length + } + + func aspectFit(largestDimension length: CGFloat) -> CGSize { + let aspectRatio = width / height + if width > height { + let width = min(self.width, length) + return CGSize(width: width, height: round(width / aspectRatio)) + } else { + let height = min(self.height, length) + return CGSize(width: round(height * aspectRatio), height: height) + } + } +} diff --git a/FirebaseVertexAI/Sample/GenerativeAISample.xcodeproj/project.pbxproj b/FirebaseVertexAI/Sample/GenerativeAISample.xcodeproj/project.pbxproj new file mode 100644 index 00000000000..20f07a5acca --- /dev/null +++ b/FirebaseVertexAI/Sample/GenerativeAISample.xcodeproj/project.pbxproj @@ -0,0 +1,1053 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 60; + objects = { + +/* Begin PBXBuildFile section */ + 869200AB2B86BE3100482873 /* FirebaseVertexAI in Frameworks */ = {isa = PBXBuildFile; productRef = 869200AA2B86BE3100482873 /* FirebaseVertexAI */; }; + 869200AD2B86BEA100482873 /* FirebaseVertexAI in Frameworks */ = {isa = PBXBuildFile; productRef = 869200AC2B86BEA100482873 /* FirebaseVertexAI */; }; + 869200AF2B86BFBC00482873 /* FirebaseVertexAI in Frameworks */ = {isa = PBXBuildFile; productRef = 869200AE2B86BFBC00482873 /* FirebaseVertexAI */; }; + 869200B12B86BFF500482873 /* FirebaseVertexAI in Frameworks */ = {isa = PBXBuildFile; productRef = 869200B02B86BFF500482873 /* FirebaseVertexAI */; }; + 869200B32B879C4F00482873 /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 869200B22B879C4F00482873 /* GoogleService-Info.plist */; }; + 869200B42B879C4F00482873 /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 869200B22B879C4F00482873 /* GoogleService-Info.plist */; }; + 869200B52B879C4F00482873 /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 869200B22B879C4F00482873 /* GoogleService-Info.plist */; }; + 869200B62B879C4F00482873 /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 869200B22B879C4F00482873 /* GoogleService-Info.plist */; }; + 880266762B0FC39000CF7CB6 /* PhotoReasoningViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8802666F2B0FC39000CF7CB6 /* PhotoReasoningViewModel.swift */; }; + 880266792B0FC39000CF7CB6 /* PhotoReasoningScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 880266752B0FC39000CF7CB6 /* PhotoReasoningScreen.swift */; }; + 88209C1F2B0FBDC300F64795 /* SummarizeScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88209C1B2B0FBDC300F64795 /* SummarizeScreen.swift */; }; + 88209C202B0FBDC300F64795 /* SummarizeViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88209C1D2B0FBDC300F64795 /* SummarizeViewModel.swift */; }; + 88209C242B0FBE1700F64795 /* MarkdownUI in Frameworks */ = {isa = PBXBuildFile; productRef = 88209C232B0FBE1700F64795 /* MarkdownUI */; }; + 88263BEF2B239BFE008AB09B /* ErrorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88263BEE2B239BFE008AB09B /* ErrorView.swift */; }; + 88263BF02B239C09008AB09B /* ErrorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88263BEE2B239BFE008AB09B /* ErrorView.swift */; }; + 88263BF12B239C11008AB09B /* ErrorDetailsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 889873842B208563005B4896 /* ErrorDetailsView.swift */; }; + 8848C8332B0D04BC007B434F /* GenerativeAISampleApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8848C8322B0D04BC007B434F /* GenerativeAISampleApp.swift */; }; + 8848C8352B0D04BC007B434F /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8848C8342B0D04BC007B434F /* ContentView.swift */; }; + 8848C8372B0D04BD007B434F /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 8848C8362B0D04BD007B434F /* Assets.xcassets */; }; + 8848C83A2B0D04BD007B434F /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 8848C8392B0D04BD007B434F /* Preview Assets.xcassets */; }; + 8848C8472B0D051E007B434F /* GenerativeAITextSampleApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8848C8462B0D051E007B434F /* GenerativeAITextSampleApp.swift */; }; + 8848C84B2B0D051F007B434F /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 8848C84A2B0D051F007B434F /* Assets.xcassets */; }; + 8848C84E2B0D051F007B434F /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 8848C84D2B0D051F007B434F /* Preview Assets.xcassets */; }; + 8848C8592B0D056C007B434F /* GenerativeAIMultimodalSampleApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8848C8582B0D056C007B434F /* GenerativeAIMultimodalSampleApp.swift */; }; + 8848C85D2B0D056D007B434F /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 8848C85C2B0D056D007B434F /* Assets.xcassets */; }; + 8848C8602B0D056D007B434F /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 8848C85F2B0D056D007B434F /* Preview Assets.xcassets */; }; + 886F95D52B17BA010036F07A /* SummarizeScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88209C1B2B0FBDC300F64795 /* SummarizeScreen.swift */; }; + 886F95D62B17BA010036F07A /* SummarizeViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88209C1D2B0FBDC300F64795 /* SummarizeViewModel.swift */; }; + 886F95D82B17BA420036F07A /* MarkdownUI in Frameworks */ = {isa = PBXBuildFile; productRef = 886F95D72B17BA420036F07A /* MarkdownUI */; }; + 886F95DB2B17BAEF0036F07A /* PhotoReasoningViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8802666F2B0FC39000CF7CB6 /* PhotoReasoningViewModel.swift */; }; + 886F95DC2B17BAEF0036F07A /* PhotoReasoningScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 880266752B0FC39000CF7CB6 /* PhotoReasoningScreen.swift */; }; + 886F95DD2B17D5010036F07A /* MessageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88E10F5A2B11133E00C08E95 /* MessageView.swift */; }; + 886F95DE2B17D5010036F07A /* ChatMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88E10F582B11131900C08E95 /* ChatMessage.swift */; }; + 886F95DF2B17D5010036F07A /* BouncingDots.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88E10F5C2B11135000C08E95 /* BouncingDots.swift */; }; + 886F95E02B17D5010036F07A /* ConversationViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88E10F562B1112F600C08E95 /* ConversationViewModel.swift */; }; + 886F95E12B17D5010036F07A /* ConversationScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88E10F542B1112CA00C08E95 /* ConversationScreen.swift */; }; + 886F95E32B17D6630036F07A /* GenerativeAIUIComponents in Frameworks */ = {isa = PBXBuildFile; productRef = 886F95E22B17D6630036F07A /* GenerativeAIUIComponents */; }; + 889873852B208563005B4896 /* ErrorDetailsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 889873842B208563005B4896 /* ErrorDetailsView.swift */; }; + 88B8A91E2B0FC55100424728 /* MarkdownUI in Frameworks */ = {isa = PBXBuildFile; productRef = 88B8A91D2B0FC55100424728 /* MarkdownUI */; }; + 88B8A9372B0FCBE700424728 /* GenerativeAIUIComponents in Frameworks */ = {isa = PBXBuildFile; productRef = 88B8A9362B0FCBE700424728 /* GenerativeAIUIComponents */; }; + 88D9474D2B14F27E008B5580 /* MarkdownUI in Frameworks */ = {isa = PBXBuildFile; productRef = 88D9474C2B14F27E008B5580 /* MarkdownUI */; }; + 88E10F452B110D5300C08E95 /* ChatSampleApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88E10F442B110D5300C08E95 /* ChatSampleApp.swift */; }; + 88E10F492B110D5400C08E95 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 88E10F482B110D5400C08E95 /* Assets.xcassets */; }; + 88E10F4C2B110D5400C08E95 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 88E10F4B2B110D5400C08E95 /* Preview Assets.xcassets */; }; + 88E10F552B1112CA00C08E95 /* ConversationScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88E10F542B1112CA00C08E95 /* ConversationScreen.swift */; }; + 88E10F572B1112F600C08E95 /* ConversationViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88E10F562B1112F600C08E95 /* ConversationViewModel.swift */; }; + 88E10F592B11131900C08E95 /* ChatMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88E10F582B11131900C08E95 /* ChatMessage.swift */; }; + 88E10F5B2B11133E00C08E95 /* MessageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88E10F5A2B11133E00C08E95 /* MessageView.swift */; }; + 88E10F5D2B11135000C08E95 /* BouncingDots.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88E10F5C2B11135000C08E95 /* BouncingDots.swift */; }; + CB0DFFD52B2B4F08006E109D /* GenerativeAIUIComponents in Frameworks */ = {isa = PBXBuildFile; productRef = CB0DFFD42B2B4F08006E109D /* GenerativeAIUIComponents */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + 869200B22B879C4F00482873 /* GoogleService-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "GoogleService-Info.plist"; sourceTree = ""; }; + 8802666F2B0FC39000CF7CB6 /* PhotoReasoningViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PhotoReasoningViewModel.swift; sourceTree = ""; }; + 880266752B0FC39000CF7CB6 /* PhotoReasoningScreen.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PhotoReasoningScreen.swift; sourceTree = ""; }; + 88209C1B2B0FBDC300F64795 /* SummarizeScreen.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SummarizeScreen.swift; sourceTree = ""; }; + 88209C1D2B0FBDC300F64795 /* SummarizeViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SummarizeViewModel.swift; sourceTree = ""; }; + 88263BEE2B239BFE008AB09B /* ErrorView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ErrorView.swift; sourceTree = ""; }; + 8848C82F2B0D04BC007B434F /* GenerativeAISample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = GenerativeAISample.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 8848C8322B0D04BC007B434F /* GenerativeAISampleApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GenerativeAISampleApp.swift; sourceTree = ""; }; + 8848C8342B0D04BC007B434F /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; + 8848C8362B0D04BD007B434F /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 8848C8392B0D04BD007B434F /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; + 8848C8442B0D051E007B434F /* GenerativeAITextSample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = GenerativeAITextSample.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 8848C8462B0D051E007B434F /* GenerativeAITextSampleApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GenerativeAITextSampleApp.swift; sourceTree = ""; }; + 8848C84A2B0D051F007B434F /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 8848C84D2B0D051F007B434F /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; + 8848C8562B0D056C007B434F /* GenerativeAIMultimodalSample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = GenerativeAIMultimodalSample.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 8848C8582B0D056C007B434F /* GenerativeAIMultimodalSampleApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GenerativeAIMultimodalSampleApp.swift; sourceTree = ""; }; + 8848C85C2B0D056D007B434F /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 8848C85F2B0D056D007B434F /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; + 889873842B208563005B4896 /* ErrorDetailsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ErrorDetailsView.swift; sourceTree = ""; }; + 88B8A9352B0FCBA700424728 /* GenerativeAIUIComponents */ = {isa = PBXFileReference; lastKnownFileType = wrapper; path = GenerativeAIUIComponents; sourceTree = ""; }; + 88E10F422B110D5300C08E95 /* ChatSample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = ChatSample.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 88E10F442B110D5300C08E95 /* ChatSampleApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatSampleApp.swift; sourceTree = ""; }; + 88E10F482B110D5400C08E95 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 88E10F4B2B110D5400C08E95 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; + 88E10F542B1112CA00C08E95 /* ConversationScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConversationScreen.swift; sourceTree = ""; }; + 88E10F562B1112F600C08E95 /* ConversationViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConversationViewModel.swift; sourceTree = ""; }; + 88E10F582B11131900C08E95 /* ChatMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatMessage.swift; sourceTree = ""; }; + 88E10F5A2B11133E00C08E95 /* MessageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageView.swift; sourceTree = ""; }; + 88E10F5C2B11135000C08E95 /* BouncingDots.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BouncingDots.swift; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 8848C82C2B0D04BC007B434F /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 886F95D82B17BA420036F07A /* MarkdownUI in Frameworks */, + 869200AB2B86BE3100482873 /* FirebaseVertexAI in Frameworks */, + 886F95E32B17D6630036F07A /* GenerativeAIUIComponents in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 8848C8412B0D051E007B434F /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 88209C242B0FBE1700F64795 /* MarkdownUI in Frameworks */, + 869200B12B86BFF500482873 /* FirebaseVertexAI in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 8848C8532B0D056C007B434F /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 88B8A91E2B0FC55100424728 /* MarkdownUI in Frameworks */, + 869200AF2B86BFBC00482873 /* FirebaseVertexAI in Frameworks */, + 88B8A9372B0FCBE700424728 /* GenerativeAIUIComponents in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 88E10F3F2B110D5300C08E95 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 88D9474D2B14F27E008B5580 /* MarkdownUI in Frameworks */, + 869200AD2B86BEA100482873 /* FirebaseVertexAI in Frameworks */, + CB0DFFD52B2B4F08006E109D /* GenerativeAIUIComponents in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 8802666E2B0FC39000CF7CB6 /* ViewModels */ = { + isa = PBXGroup; + children = ( + 8802666F2B0FC39000CF7CB6 /* PhotoReasoningViewModel.swift */, + ); + path = ViewModels; + sourceTree = ""; + }; + 880266742B0FC39000CF7CB6 /* Screens */ = { + isa = PBXGroup; + children = ( + 880266752B0FC39000CF7CB6 /* PhotoReasoningScreen.swift */, + ); + path = Screens; + sourceTree = ""; + }; + 88209C1A2B0FBDC300F64795 /* Screens */ = { + isa = PBXGroup; + children = ( + 88209C1B2B0FBDC300F64795 /* SummarizeScreen.swift */, + ); + path = Screens; + sourceTree = ""; + }; + 88209C1C2B0FBDC300F64795 /* ViewModels */ = { + isa = PBXGroup; + children = ( + 88209C1D2B0FBDC300F64795 /* SummarizeViewModel.swift */, + ); + path = ViewModels; + sourceTree = ""; + }; + 88209C222B0FBE1700F64795 /* Frameworks */ = { + isa = PBXGroup; + children = ( + ); + name = Frameworks; + sourceTree = ""; + }; + 8848C8262B0D04BC007B434F = { + isa = PBXGroup; + children = ( + 88B8A9352B0FCBA700424728 /* GenerativeAIUIComponents */, + 869200B22B879C4F00482873 /* GoogleService-Info.plist */, + 8848C8312B0D04BC007B434F /* GenerativeAISample */, + 8848C8452B0D051E007B434F /* GenerativeAITextSample */, + 8848C8572B0D056C007B434F /* GenerativeAIMultimodalSample */, + 88E10F432B110D5300C08E95 /* ChatSample */, + 8848C8302B0D04BC007B434F /* Products */, + 88209C222B0FBE1700F64795 /* Frameworks */, + ); + sourceTree = ""; + }; + 8848C8302B0D04BC007B434F /* Products */ = { + isa = PBXGroup; + children = ( + 8848C82F2B0D04BC007B434F /* GenerativeAISample.app */, + 8848C8442B0D051E007B434F /* GenerativeAITextSample.app */, + 8848C8562B0D056C007B434F /* GenerativeAIMultimodalSample.app */, + 88E10F422B110D5300C08E95 /* ChatSample.app */, + ); + name = Products; + sourceTree = ""; + }; + 8848C8312B0D04BC007B434F /* GenerativeAISample */ = { + isa = PBXGroup; + children = ( + 8848C8322B0D04BC007B434F /* GenerativeAISampleApp.swift */, + 8848C8342B0D04BC007B434F /* ContentView.swift */, + 8848C8362B0D04BD007B434F /* Assets.xcassets */, + 8848C8382B0D04BD007B434F /* Preview Content */, + ); + path = GenerativeAISample; + sourceTree = ""; + }; + 8848C8382B0D04BD007B434F /* Preview Content */ = { + isa = PBXGroup; + children = ( + 8848C8392B0D04BD007B434F /* Preview Assets.xcassets */, + ); + path = "Preview Content"; + sourceTree = ""; + }; + 8848C8452B0D051E007B434F /* GenerativeAITextSample */ = { + isa = PBXGroup; + children = ( + 88209C1C2B0FBDC300F64795 /* ViewModels */, + 88209C1A2B0FBDC300F64795 /* Screens */, + 8848C8462B0D051E007B434F /* GenerativeAITextSampleApp.swift */, + 8848C84A2B0D051F007B434F /* Assets.xcassets */, + 8848C84C2B0D051F007B434F /* Preview Content */, + ); + path = GenerativeAITextSample; + sourceTree = ""; + }; + 8848C84C2B0D051F007B434F /* Preview Content */ = { + isa = PBXGroup; + children = ( + 8848C84D2B0D051F007B434F /* Preview Assets.xcassets */, + ); + path = "Preview Content"; + sourceTree = ""; + }; + 8848C8572B0D056C007B434F /* GenerativeAIMultimodalSample */ = { + isa = PBXGroup; + children = ( + 8802666E2B0FC39000CF7CB6 /* ViewModels */, + 880266742B0FC39000CF7CB6 /* Screens */, + 8848C8582B0D056C007B434F /* GenerativeAIMultimodalSampleApp.swift */, + 8848C85C2B0D056D007B434F /* Assets.xcassets */, + 8848C85E2B0D056D007B434F /* Preview Content */, + ); + path = GenerativeAIMultimodalSample; + sourceTree = ""; + }; + 8848C85E2B0D056D007B434F /* Preview Content */ = { + isa = PBXGroup; + children = ( + 8848C85F2B0D056D007B434F /* Preview Assets.xcassets */, + ); + path = "Preview Content"; + sourceTree = ""; + }; + 88E10F432B110D5300C08E95 /* ChatSample */ = { + isa = PBXGroup; + children = ( + 88E10F522B11124A00C08E95 /* Models */, + 88E10F502B11123600C08E95 /* ViewModels */, + 88E10F512B11124100C08E95 /* Views */, + 88E10F532B1112B900C08E95 /* Screens */, + 88E10F442B110D5300C08E95 /* ChatSampleApp.swift */, + 88E10F482B110D5400C08E95 /* Assets.xcassets */, + 88E10F4A2B110D5400C08E95 /* Preview Content */, + ); + path = ChatSample; + sourceTree = ""; + }; + 88E10F4A2B110D5400C08E95 /* Preview Content */ = { + isa = PBXGroup; + children = ( + 88E10F4B2B110D5400C08E95 /* Preview Assets.xcassets */, + ); + path = "Preview Content"; + sourceTree = ""; + }; + 88E10F502B11123600C08E95 /* ViewModels */ = { + isa = PBXGroup; + children = ( + 88E10F562B1112F600C08E95 /* ConversationViewModel.swift */, + ); + path = ViewModels; + sourceTree = ""; + }; + 88E10F512B11124100C08E95 /* Views */ = { + isa = PBXGroup; + children = ( + 88263BEE2B239BFE008AB09B /* ErrorView.swift */, + 88E10F5A2B11133E00C08E95 /* MessageView.swift */, + 88E10F5C2B11135000C08E95 /* BouncingDots.swift */, + 889873842B208563005B4896 /* ErrorDetailsView.swift */, + ); + path = Views; + sourceTree = ""; + }; + 88E10F522B11124A00C08E95 /* Models */ = { + isa = PBXGroup; + children = ( + 88E10F582B11131900C08E95 /* ChatMessage.swift */, + ); + path = Models; + sourceTree = ""; + }; + 88E10F532B1112B900C08E95 /* Screens */ = { + isa = PBXGroup; + children = ( + 88E10F542B1112CA00C08E95 /* ConversationScreen.swift */, + ); + path = Screens; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 8848C82E2B0D04BC007B434F /* GenerativeAISample */ = { + isa = PBXNativeTarget; + buildConfigurationList = 8848C83D2B0D04BD007B434F /* Build configuration list for PBXNativeTarget "GenerativeAISample" */; + buildPhases = ( + 8848C82B2B0D04BC007B434F /* Sources */, + 8848C82C2B0D04BC007B434F /* Frameworks */, + 8848C82D2B0D04BC007B434F /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = GenerativeAISample; + packageProductDependencies = ( + 886F95D72B17BA420036F07A /* MarkdownUI */, + 886F95E22B17D6630036F07A /* GenerativeAIUIComponents */, + 869200AA2B86BE3100482873 /* FirebaseVertexAI */, + ); + productName = GenerativeAISample; + productReference = 8848C82F2B0D04BC007B434F /* GenerativeAISample.app */; + productType = "com.apple.product-type.application"; + }; + 8848C8432B0D051E007B434F /* GenerativeAITextSample */ = { + isa = PBXNativeTarget; + buildConfigurationList = 8848C84F2B0D051F007B434F /* Build configuration list for PBXNativeTarget "GenerativeAITextSample" */; + buildPhases = ( + 8848C8402B0D051E007B434F /* Sources */, + 8848C8412B0D051E007B434F /* Frameworks */, + 8848C8422B0D051E007B434F /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = GenerativeAITextSample; + packageProductDependencies = ( + 88209C232B0FBE1700F64795 /* MarkdownUI */, + 869200B02B86BFF500482873 /* FirebaseVertexAI */, + ); + productName = GenerativeAITextSample; + productReference = 8848C8442B0D051E007B434F /* GenerativeAITextSample.app */; + productType = "com.apple.product-type.application"; + }; + 8848C8552B0D056C007B434F /* GenerativeAIMultimodalSample */ = { + isa = PBXNativeTarget; + buildConfigurationList = 8848C8612B0D056D007B434F /* Build configuration list for PBXNativeTarget "GenerativeAIMultimodalSample" */; + buildPhases = ( + 8848C8522B0D056C007B434F /* Sources */, + 8848C8532B0D056C007B434F /* Frameworks */, + 8848C8542B0D056C007B434F /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = GenerativeAIMultimodalSample; + packageProductDependencies = ( + 88B8A91D2B0FC55100424728 /* MarkdownUI */, + 88B8A9362B0FCBE700424728 /* GenerativeAIUIComponents */, + 869200AE2B86BFBC00482873 /* FirebaseVertexAI */, + ); + productName = GenerativeAIMultimodalSample; + productReference = 8848C8562B0D056C007B434F /* GenerativeAIMultimodalSample.app */; + productType = "com.apple.product-type.application"; + }; + 88E10F412B110D5300C08E95 /* ChatSample */ = { + isa = PBXNativeTarget; + buildConfigurationList = 88E10F4F2B110D5400C08E95 /* Build configuration list for PBXNativeTarget "ChatSample" */; + buildPhases = ( + 88E10F3E2B110D5300C08E95 /* Sources */, + 88E10F3F2B110D5300C08E95 /* Frameworks */, + 88E10F402B110D5300C08E95 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = ChatSample; + packageProductDependencies = ( + 88D9474C2B14F27E008B5580 /* MarkdownUI */, + CB0DFFD42B2B4F08006E109D /* GenerativeAIUIComponents */, + 869200AC2B86BEA100482873 /* FirebaseVertexAI */, + ); + productName = ChatSample; + productReference = 88E10F422B110D5300C08E95 /* ChatSample.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 8848C8272B0D04BC007B434F /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = 1; + LastSwiftUpdateCheck = 1510; + LastUpgradeCheck = 1510; + TargetAttributes = { + 8848C82E2B0D04BC007B434F = { + CreatedOnToolsVersion = 15.1; + }; + 8848C8432B0D051E007B434F = { + CreatedOnToolsVersion = 15.1; + }; + 8848C8552B0D056C007B434F = { + CreatedOnToolsVersion = 15.1; + }; + 88E10F412B110D5300C08E95 = { + CreatedOnToolsVersion = 15.1; + }; + }; + }; + buildConfigurationList = 8848C82A2B0D04BC007B434F /* Build configuration list for PBXProject "GenerativeAISample" */; + compatibilityVersion = "Xcode 14.0"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 8848C8262B0D04BC007B434F; + packageReferences = ( + 88209C212B0FBDF700F64795 /* XCRemoteSwiftPackageReference "swift-markdown-ui" */, + DEA09AC32B1FCE22001962D9 /* XCRemoteSwiftPackageReference "NetworkImage" */, + 869200A92B86BE3100482873 /* XCLocalSwiftPackageReference "../.." */, + ); + productRefGroup = 8848C8302B0D04BC007B434F /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 8848C82E2B0D04BC007B434F /* GenerativeAISample */, + 8848C8432B0D051E007B434F /* GenerativeAITextSample */, + 8848C8552B0D056C007B434F /* GenerativeAIMultimodalSample */, + 88E10F412B110D5300C08E95 /* ChatSample */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 8848C82D2B0D04BC007B434F /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 8848C83A2B0D04BD007B434F /* Preview Assets.xcassets in Resources */, + 8848C8372B0D04BD007B434F /* Assets.xcassets in Resources */, + 869200B32B879C4F00482873 /* GoogleService-Info.plist in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 8848C8422B0D051E007B434F /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 8848C84E2B0D051F007B434F /* Preview Assets.xcassets in Resources */, + 8848C84B2B0D051F007B434F /* Assets.xcassets in Resources */, + 869200B42B879C4F00482873 /* GoogleService-Info.plist in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 8848C8542B0D056C007B434F /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 8848C8602B0D056D007B434F /* Preview Assets.xcassets in Resources */, + 8848C85D2B0D056D007B434F /* Assets.xcassets in Resources */, + 869200B52B879C4F00482873 /* GoogleService-Info.plist in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 88E10F402B110D5300C08E95 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 88E10F4C2B110D5400C08E95 /* Preview Assets.xcassets in Resources */, + 88E10F492B110D5400C08E95 /* Assets.xcassets in Resources */, + 869200B62B879C4F00482873 /* GoogleService-Info.plist in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 8848C82B2B0D04BC007B434F /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 886F95DF2B17D5010036F07A /* BouncingDots.swift in Sources */, + 886F95DE2B17D5010036F07A /* ChatMessage.swift in Sources */, + 88263BF12B239C11008AB09B /* ErrorDetailsView.swift in Sources */, + 8848C8352B0D04BC007B434F /* ContentView.swift in Sources */, + 886F95D52B17BA010036F07A /* SummarizeScreen.swift in Sources */, + 8848C8332B0D04BC007B434F /* GenerativeAISampleApp.swift in Sources */, + 886F95E02B17D5010036F07A /* ConversationViewModel.swift in Sources */, + 886F95DD2B17D5010036F07A /* MessageView.swift in Sources */, + 886F95DC2B17BAEF0036F07A /* PhotoReasoningScreen.swift in Sources */, + 886F95DB2B17BAEF0036F07A /* PhotoReasoningViewModel.swift in Sources */, + 886F95E12B17D5010036F07A /* ConversationScreen.swift in Sources */, + 88263BF02B239C09008AB09B /* ErrorView.swift in Sources */, + 886F95D62B17BA010036F07A /* SummarizeViewModel.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 8848C8402B0D051E007B434F /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 88209C1F2B0FBDC300F64795 /* SummarizeScreen.swift in Sources */, + 8848C8472B0D051E007B434F /* GenerativeAITextSampleApp.swift in Sources */, + 88209C202B0FBDC300F64795 /* SummarizeViewModel.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 8848C8522B0D056C007B434F /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 880266762B0FC39000CF7CB6 /* PhotoReasoningViewModel.swift in Sources */, + 880266792B0FC39000CF7CB6 /* PhotoReasoningScreen.swift in Sources */, + 8848C8592B0D056C007B434F /* GenerativeAIMultimodalSampleApp.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 88E10F3E2B110D5300C08E95 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 88E10F5B2B11133E00C08E95 /* MessageView.swift in Sources */, + 88E10F572B1112F600C08E95 /* ConversationViewModel.swift in Sources */, + 88E10F552B1112CA00C08E95 /* ConversationScreen.swift in Sources */, + 88E10F592B11131900C08E95 /* ChatMessage.swift in Sources */, + 88E10F5D2B11135000C08E95 /* BouncingDots.swift in Sources */, + 88E10F452B110D5300C08E95 /* ChatSampleApp.swift in Sources */, + 88263BEF2B239BFE008AB09B /* ErrorView.swift in Sources */, + 889873852B208563005B4896 /* ErrorDetailsView.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + 8848C83B2B0D04BD007B434F /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 16.0; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + 8848C83C2B0D04BD007B434F /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 16.0; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 8848C83E2B0D04BD007B434F /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_ASSET_PATHS = "\"GenerativeAISample/Preview Content\""; + DEVELOPMENT_TEAM = ""; + ENABLE_PREVIEWS = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; + INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; + INFOPLIST_KEY_UILaunchScreen_Generation = YES; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.google.generativeai.GenerativeAISample; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 8848C83F2B0D04BD007B434F /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_ASSET_PATHS = "\"GenerativeAISample/Preview Content\""; + DEVELOPMENT_TEAM = ""; + ENABLE_PREVIEWS = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; + INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; + INFOPLIST_KEY_UILaunchScreen_Generation = YES; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.google.generativeai.GenerativeAISample; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; + 8848C8502B0D051F007B434F /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_ASSET_PATHS = "\"GenerativeAITextSample/Preview Content\""; + DEVELOPMENT_TEAM = ""; + ENABLE_PREVIEWS = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; + INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; + INFOPLIST_KEY_UILaunchScreen_Generation = YES; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + IPHONEOS_DEPLOYMENT_TARGET = 16.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.google.generativeai.GenerativeAITextSample; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 8848C8512B0D051F007B434F /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_ASSET_PATHS = "\"GenerativeAITextSample/Preview Content\""; + DEVELOPMENT_TEAM = ""; + ENABLE_PREVIEWS = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; + INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; + INFOPLIST_KEY_UILaunchScreen_Generation = YES; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + IPHONEOS_DEPLOYMENT_TARGET = 16.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.google.generativeai.GenerativeAITextSample; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; + 8848C8622B0D056D007B434F /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_ASSET_PATHS = "\"GenerativeAIMultimodalSample/Preview Content\""; + DEVELOPMENT_TEAM = ""; + ENABLE_PREVIEWS = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; + INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; + INFOPLIST_KEY_UILaunchScreen_Generation = YES; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + IPHONEOS_DEPLOYMENT_TARGET = 16.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.google.generativeai.GenerativeAIMultimodalSample; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 8848C8632B0D056D007B434F /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_ASSET_PATHS = "\"GenerativeAIMultimodalSample/Preview Content\""; + DEVELOPMENT_TEAM = ""; + ENABLE_PREVIEWS = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; + INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; + INFOPLIST_KEY_UILaunchScreen_Generation = YES; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + IPHONEOS_DEPLOYMENT_TARGET = 16.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.google.generativeai.GenerativeAIMultimodalSample; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; + 88E10F4D2B110D5400C08E95 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_ASSET_PATHS = "\"ChatSample/Preview Content\""; + DEVELOPMENT_TEAM = ""; + ENABLE_PREVIEWS = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; + INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; + INFOPLIST_KEY_UILaunchScreen_Generation = YES; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + IPHONEOS_DEPLOYMENT_TARGET = 16.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.google.generativeai.ChatSample; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 88E10F4E2B110D5400C08E95 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_ASSET_PATHS = "\"ChatSample/Preview Content\""; + DEVELOPMENT_TEAM = ""; + ENABLE_PREVIEWS = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; + INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; + INFOPLIST_KEY_UILaunchScreen_Generation = YES; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + IPHONEOS_DEPLOYMENT_TARGET = 16.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.google.generativeai.ChatSample; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 8848C82A2B0D04BC007B434F /* Build configuration list for PBXProject "GenerativeAISample" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 8848C83B2B0D04BD007B434F /* Debug */, + 8848C83C2B0D04BD007B434F /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 8848C83D2B0D04BD007B434F /* Build configuration list for PBXNativeTarget "GenerativeAISample" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 8848C83E2B0D04BD007B434F /* Debug */, + 8848C83F2B0D04BD007B434F /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 8848C84F2B0D051F007B434F /* Build configuration list for PBXNativeTarget "GenerativeAITextSample" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 8848C8502B0D051F007B434F /* Debug */, + 8848C8512B0D051F007B434F /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 8848C8612B0D056D007B434F /* Build configuration list for PBXNativeTarget "GenerativeAIMultimodalSample" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 8848C8622B0D056D007B434F /* Debug */, + 8848C8632B0D056D007B434F /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 88E10F4F2B110D5400C08E95 /* Build configuration list for PBXNativeTarget "ChatSample" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 88E10F4D2B110D5400C08E95 /* Debug */, + 88E10F4E2B110D5400C08E95 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + +/* Begin XCLocalSwiftPackageReference section */ + 869200A92B86BE3100482873 /* XCLocalSwiftPackageReference "../.." */ = { + isa = XCLocalSwiftPackageReference; + relativePath = ../..; + }; +/* End XCLocalSwiftPackageReference section */ + +/* Begin XCRemoteSwiftPackageReference section */ + 88209C212B0FBDF700F64795 /* XCRemoteSwiftPackageReference "swift-markdown-ui" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/gonzalezreal/swift-markdown-ui"; + requirement = { + kind = revision; + revision = 5df8a4adedd6ae4eb2455ef60ff75183984daeb8; + }; + }; + DEA09AC32B1FCE22001962D9 /* XCRemoteSwiftPackageReference "NetworkImage" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/gonzalezreal/NetworkImage"; + requirement = { + kind = revision; + revision = 7aff8d1b31148d32c5933d75557d42f6323ee3d1; + }; + }; +/* End XCRemoteSwiftPackageReference section */ + +/* Begin XCSwiftPackageProductDependency section */ + 869200AA2B86BE3100482873 /* FirebaseVertexAI */ = { + isa = XCSwiftPackageProductDependency; + productName = FirebaseVertexAI; + }; + 869200AC2B86BEA100482873 /* FirebaseVertexAI */ = { + isa = XCSwiftPackageProductDependency; + productName = FirebaseVertexAI; + }; + 869200AE2B86BFBC00482873 /* FirebaseVertexAI */ = { + isa = XCSwiftPackageProductDependency; + productName = FirebaseVertexAI; + }; + 869200B02B86BFF500482873 /* FirebaseVertexAI */ = { + isa = XCSwiftPackageProductDependency; + productName = FirebaseVertexAI; + }; + 88209C232B0FBE1700F64795 /* MarkdownUI */ = { + isa = XCSwiftPackageProductDependency; + package = 88209C212B0FBDF700F64795 /* XCRemoteSwiftPackageReference "swift-markdown-ui" */; + productName = MarkdownUI; + }; + 886F95D72B17BA420036F07A /* MarkdownUI */ = { + isa = XCSwiftPackageProductDependency; + package = 88209C212B0FBDF700F64795 /* XCRemoteSwiftPackageReference "swift-markdown-ui" */; + productName = MarkdownUI; + }; + 886F95E22B17D6630036F07A /* GenerativeAIUIComponents */ = { + isa = XCSwiftPackageProductDependency; + productName = GenerativeAIUIComponents; + }; + 88B8A91D2B0FC55100424728 /* MarkdownUI */ = { + isa = XCSwiftPackageProductDependency; + package = 88209C212B0FBDF700F64795 /* XCRemoteSwiftPackageReference "swift-markdown-ui" */; + productName = MarkdownUI; + }; + 88B8A9362B0FCBE700424728 /* GenerativeAIUIComponents */ = { + isa = XCSwiftPackageProductDependency; + productName = GenerativeAIUIComponents; + }; + 88D9474C2B14F27E008B5580 /* MarkdownUI */ = { + isa = XCSwiftPackageProductDependency; + package = 88209C212B0FBDF700F64795 /* XCRemoteSwiftPackageReference "swift-markdown-ui" */; + productName = MarkdownUI; + }; + CB0DFFD42B2B4F08006E109D /* GenerativeAIUIComponents */ = { + isa = XCSwiftPackageProductDependency; + productName = GenerativeAIUIComponents; + }; +/* End XCSwiftPackageProductDependency section */ + }; + rootObject = 8848C8272B0D04BC007B434F /* Project object */; +} diff --git a/FirebaseVertexAI/Sample/GenerativeAISample/Assets.xcassets/AccentColor.colorset/Contents.json b/FirebaseVertexAI/Sample/GenerativeAISample/Assets.xcassets/AccentColor.colorset/Contents.json new file mode 100644 index 00000000000..eb878970081 --- /dev/null +++ b/FirebaseVertexAI/Sample/GenerativeAISample/Assets.xcassets/AccentColor.colorset/Contents.json @@ -0,0 +1,11 @@ +{ + "colors" : [ + { + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/FirebaseVertexAI/Sample/GenerativeAISample/Assets.xcassets/AppIcon.appiconset/Contents.json b/FirebaseVertexAI/Sample/GenerativeAISample/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 00000000000..13613e3ee1a --- /dev/null +++ b/FirebaseVertexAI/Sample/GenerativeAISample/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,13 @@ +{ + "images" : [ + { + "idiom" : "universal", + "platform" : "ios", + "size" : "1024x1024" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/FirebaseVertexAI/Sample/GenerativeAISample/Assets.xcassets/Contents.json b/FirebaseVertexAI/Sample/GenerativeAISample/Assets.xcassets/Contents.json new file mode 100644 index 00000000000..73c00596a7f --- /dev/null +++ b/FirebaseVertexAI/Sample/GenerativeAISample/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/FirebaseVertexAI/Sample/GenerativeAISample/ContentView.swift b/FirebaseVertexAI/Sample/GenerativeAISample/ContentView.swift new file mode 100644 index 00000000000..34331bf6eb7 --- /dev/null +++ b/FirebaseVertexAI/Sample/GenerativeAISample/ContentView.swift @@ -0,0 +1,48 @@ +// Copyright 2023 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 SwiftUI + +struct ContentView: View { + @StateObject + var viewModel = ConversationViewModel() + + var body: some View { + NavigationStack { + List { + NavigationLink { + SummarizeScreen() + } label: { + Label("Text", systemImage: "doc.text") + } + NavigationLink { + PhotoReasoningScreen() + } label: { + Label("Multi-modal", systemImage: "doc.richtext") + } + NavigationLink { + ConversationScreen() + .environmentObject(viewModel) + } label: { + Label("Chat", systemImage: "ellipsis.message.fill") + } + } + .navigationTitle("Generative AI Samples") + } + } +} + +#Preview { + ContentView() +} diff --git a/FirebaseVertexAI/Sample/GenerativeAISample/GenerativeAISampleApp.swift b/FirebaseVertexAI/Sample/GenerativeAISample/GenerativeAISampleApp.swift new file mode 100644 index 00000000000..7c709111b7f --- /dev/null +++ b/FirebaseVertexAI/Sample/GenerativeAISample/GenerativeAISampleApp.swift @@ -0,0 +1,29 @@ +// Copyright 2023 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 FirebaseCore +import SwiftUI + +@main +struct GenerativeAISampleApp: App { + init() { + FirebaseApp.configure() + } + + var body: some Scene { + WindowGroup { + ContentView() + } + } +} diff --git a/FirebaseVertexAI/Sample/GenerativeAISample/Preview Content/Preview Assets.xcassets/Contents.json b/FirebaseVertexAI/Sample/GenerativeAISample/Preview Content/Preview Assets.xcassets/Contents.json new file mode 100644 index 00000000000..73c00596a7f --- /dev/null +++ b/FirebaseVertexAI/Sample/GenerativeAISample/Preview Content/Preview Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/FirebaseVertexAI/Sample/GenerativeAITextSample/Assets.xcassets/AccentColor.colorset/Contents.json b/FirebaseVertexAI/Sample/GenerativeAITextSample/Assets.xcassets/AccentColor.colorset/Contents.json new file mode 100644 index 00000000000..eb878970081 --- /dev/null +++ b/FirebaseVertexAI/Sample/GenerativeAITextSample/Assets.xcassets/AccentColor.colorset/Contents.json @@ -0,0 +1,11 @@ +{ + "colors" : [ + { + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/FirebaseVertexAI/Sample/GenerativeAITextSample/Assets.xcassets/AppIcon.appiconset/Contents.json b/FirebaseVertexAI/Sample/GenerativeAITextSample/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 00000000000..13613e3ee1a --- /dev/null +++ b/FirebaseVertexAI/Sample/GenerativeAITextSample/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,13 @@ +{ + "images" : [ + { + "idiom" : "universal", + "platform" : "ios", + "size" : "1024x1024" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/FirebaseVertexAI/Sample/GenerativeAITextSample/Assets.xcassets/Contents.json b/FirebaseVertexAI/Sample/GenerativeAITextSample/Assets.xcassets/Contents.json new file mode 100644 index 00000000000..73c00596a7f --- /dev/null +++ b/FirebaseVertexAI/Sample/GenerativeAITextSample/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/FirebaseVertexAI/Sample/GenerativeAITextSample/GenerativeAITextSampleApp.swift b/FirebaseVertexAI/Sample/GenerativeAITextSample/GenerativeAITextSampleApp.swift new file mode 100644 index 00000000000..c800e1f7be8 --- /dev/null +++ b/FirebaseVertexAI/Sample/GenerativeAITextSample/GenerativeAITextSampleApp.swift @@ -0,0 +1,31 @@ +// Copyright 2023 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 FirebaseCore +import SwiftUI + +@main +struct GenerativeAITextSampleApp: App { + init() { + FirebaseApp.configure() + } + + var body: some Scene { + WindowGroup { + NavigationStack { + SummarizeScreen() + } + } + } +} diff --git a/FirebaseVertexAI/Sample/GenerativeAITextSample/Preview Content/Preview Assets.xcassets/Contents.json b/FirebaseVertexAI/Sample/GenerativeAITextSample/Preview Content/Preview Assets.xcassets/Contents.json new file mode 100644 index 00000000000..73c00596a7f --- /dev/null +++ b/FirebaseVertexAI/Sample/GenerativeAITextSample/Preview Content/Preview Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/FirebaseVertexAI/Sample/GenerativeAITextSample/Screens/SummarizeScreen.swift b/FirebaseVertexAI/Sample/GenerativeAITextSample/Screens/SummarizeScreen.swift new file mode 100644 index 00000000000..8fbb89f4482 --- /dev/null +++ b/FirebaseVertexAI/Sample/GenerativeAITextSample/Screens/SummarizeScreen.swift @@ -0,0 +1,74 @@ +// Copyright 2023 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 MarkdownUI +import SwiftUI + +struct SummarizeScreen: View { + @StateObject var viewModel = SummarizeViewModel() + @State var userInput = "" + + enum FocusedField: Hashable { + case message + } + + @FocusState + var focusedField: FocusedField? + + var body: some View { + VStack { + Text("Enter some text, then tap on _Go_ to summarize it.") + HStack(alignment: .top) { + TextField("Enter text summarize", text: $userInput, axis: .vertical) + .textFieldStyle(.roundedBorder) + .onSubmit { + onSummarizeTapped() + } + Button("Go") { + onSummarizeTapped() + } + .padding(.top, 4) + } + .padding([.horizontal, .bottom]) + + List { + HStack(alignment: .top) { + if viewModel.inProgress { + ProgressView() + } else { + Image(systemName: "cloud.circle.fill") + .font(.title2) + } + + Markdown("\(viewModel.outputText)") + } + .listRowSeparator(.hidden) + } + .listStyle(.plain) + } + .navigationTitle("Text sample") + } + + private func onSummarizeTapped() { + Task { + await viewModel.summarize(inputText: userInput) + } + } +} + +#Preview { + NavigationStack { + SummarizeScreen() + } +} diff --git a/FirebaseVertexAI/Sample/GenerativeAITextSample/ViewModels/SummarizeViewModel.swift b/FirebaseVertexAI/Sample/GenerativeAITextSample/ViewModels/SummarizeViewModel.swift new file mode 100644 index 00000000000..fb5b349ac82 --- /dev/null +++ b/FirebaseVertexAI/Sample/GenerativeAITextSample/ViewModels/SummarizeViewModel.swift @@ -0,0 +1,68 @@ +// Copyright 2023 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 FirebaseVertexAI +import Foundation +import OSLog + +@MainActor +class SummarizeViewModel: ObservableObject { + private var logger = Logger(subsystem: Bundle.main.bundleIdentifier!, category: "generative-ai") + + @Published + var outputText = "" + + @Published + var errorMessage: String? + + @Published + var inProgress = false + + private var model: GenerativeModel? + + init() { + model = VertexAI.generativeModel(modelName: "gemini-1.0-pro", location: "us-central1") + } + + func summarize(inputText: String) async { + defer { + inProgress = false + } + guard let model else { + return + } + + do { + inProgress = true + errorMessage = nil + outputText = "" + + let prompt = "Summarize the following text for me: \(inputText)" + + let outputContentStream = model.generateContentStream(prompt) + + // stream response + for try await outputContent in outputContentStream { + guard let line = outputContent.text else { + return + } + + outputText = outputText + line + } + } catch { + logger.error("\(error.localizedDescription)") + errorMessage = error.localizedDescription + } + } +} diff --git a/FirebaseVertexAI/Sample/GenerativeAIUIComponents/Package.swift b/FirebaseVertexAI/Sample/GenerativeAIUIComponents/Package.swift new file mode 100644 index 00000000000..808f5f42a97 --- /dev/null +++ b/FirebaseVertexAI/Sample/GenerativeAIUIComponents/Package.swift @@ -0,0 +1,35 @@ +// swift-tools-version: 5.9 +// The swift-tools-version declares the minimum version of Swift required to build this package. + +// Copyright 2023 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 PackageDescription + +let package = Package( + name: "GenerativeAIUIComponents", + platforms: [ + .iOS(.v16), + ], + products: [ + .library( + name: "GenerativeAIUIComponents", + targets: ["GenerativeAIUIComponents"] + ), + ], + targets: [ + .target( + name: "GenerativeAIUIComponents" + ), + ] +) diff --git a/FirebaseVertexAI/Sample/GenerativeAIUIComponents/Sources/GenerativeAIUIComponents/InputField.swift b/FirebaseVertexAI/Sample/GenerativeAIUIComponents/Sources/GenerativeAIUIComponents/InputField.swift new file mode 100644 index 00000000000..317baaaf3c1 --- /dev/null +++ b/FirebaseVertexAI/Sample/GenerativeAIUIComponents/Sources/GenerativeAIUIComponents/InputField.swift @@ -0,0 +1,82 @@ +// Copyright 2023 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 SwiftUI +public struct InputField