diff --git a/.github/workflows/vertexai.yml b/.github/workflows/vertexai.yml new file mode 100644 index 00000000000..4f0880de320 --- /dev/null +++ b/.github/workflows/vertexai.yml @@ -0,0 +1,36 @@ +name: vertexai + +on: + pull_request: + paths: + - 'FirebaseVertexAI**' + - '.github/workflows/vertexai.yml' + - 'Gemfile*' + schedule: + # Run every day at 11pm (PST) - cron uses UTC times + - cron: '0 7 * * *' + +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.ref }} + cancel-in-progress: true + +jobs: + spm: + strategy: + matrix: + target: [iOS, macOS, catalyst] + os: [macos-13] + include: + - os: macos-13 + xcode: Xcode_15.2 + runs-on: ${{ matrix.os }} + env: + FIREBASECI_USE_LATEST_GOOGLEAPPMEASUREMENT: 1 + steps: + - uses: actions/checkout@v4 + - name: Xcode + run: sudo xcode-select -s /Applications/${{ matrix.xcode }}.app/Contents/Developer + - name: Initialize xcodebuild + run: scripts/setup_spm_tests.sh + - name: Build and run tests + run: scripts/third_party/travis/retry.sh scripts/build.sh FirebaseVertexAIUnit ${{ matrix.target }} spm 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/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..05cbe11250f --- /dev/null +++ b/FirebaseVertexAI/Sample/ChatSample/ViewModels/ConversationViewModel.swift @@ -0,0 +1,133 @@ +// 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.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..7b3b78753db --- /dev/null +++ b/FirebaseVertexAI/Sample/ChatSample/Views/ErrorDetailsView.swift @@ -0,0 +1,208 @@ +// 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) + } + + 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) +} 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..0afc644e4e6 --- /dev/null +++ b/FirebaseVertexAI/Sample/GenerativeAIMultimodalSample/ViewModels/PhotoReasoningViewModel.swift @@ -0,0 +1,134 @@ +// 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.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)" + + // TODO: Revert below before merging + + // var images = [any ThrowingPartsRepresentable]() + // 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) + // } + // } + // } + + // Ignore the images added in the UI and use the following image stored in GCS. + + let url = URL(string: "gs://vertex-sdk-firebase-test3-images/mount_everest.jpg") + guard let url = url else { + fatalError("Invalid image URL.") + } + let parts: [ModelContent.Part] = [.text(prompt), .fileData(mimetype: "image/jpeg", url)] + + let outputContentStream = model.generateContentStream([ModelContent(parts: parts)]) + + // TODO: Revert above before merging + + // 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..1b4a69cafb0 --- /dev/null +++ b/FirebaseVertexAI/Sample/GenerativeAISample.xcodeproj/project.pbxproj @@ -0,0 +1,1081 @@ +// !$*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 */; }; + 8D589E2B2BA8E87A0049BFCA /* FirebaseAppCheck in Frameworks */ = {isa = PBXBuildFile; productRef = 8D589E2A2BA8E87A0049BFCA /* FirebaseAppCheck */; }; + 8D589E2D2BA8EEA30049BFCA /* FirebaseAppCheck in Frameworks */ = {isa = PBXBuildFile; productRef = 8D589E2C2BA8EEA30049BFCA /* FirebaseAppCheck */; }; + 8D589E2F2BA8EEBD0049BFCA /* FirebaseAppCheck in Frameworks */ = {isa = PBXBuildFile; productRef = 8D589E2E2BA8EEBD0049BFCA /* FirebaseAppCheck */; }; + 8D589E312BA8EED80049BFCA /* FirebaseAppCheck in Frameworks */ = {isa = PBXBuildFile; productRef = 8D589E302BA8EED80049BFCA /* FirebaseAppCheck */; }; + 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 = ( + 8D589E312BA8EED80049BFCA /* FirebaseAppCheck in Frameworks */, + 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 */, + 8D589E2F2BA8EEBD0049BFCA /* FirebaseAppCheck in Frameworks */, + 869200B12B86BFF500482873 /* FirebaseVertexAI in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 8848C8532B0D056C007B434F /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 8D589E2B2BA8E87A0049BFCA /* FirebaseAppCheck in Frameworks */, + 88B8A91E2B0FC55100424728 /* MarkdownUI in Frameworks */, + 869200AF2B86BFBC00482873 /* FirebaseVertexAI in Frameworks */, + 88B8A9372B0FCBE700424728 /* GenerativeAIUIComponents in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 88E10F3F2B110D5300C08E95 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 8D589E2D2BA8EEA30049BFCA /* FirebaseAppCheck in Frameworks */, + 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 */, + 8D589E302BA8EED80049BFCA /* FirebaseAppCheck */, + ); + 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 */, + 8D589E2E2BA8EEBD0049BFCA /* FirebaseAppCheck */, + ); + 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 */, + 8D589E2A2BA8E87A0049BFCA /* FirebaseAppCheck */, + ); + 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 */, + 8D589E2C2BA8EEA30049BFCA /* FirebaseAppCheck */, + ); + 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; + }; + 8D589E2A2BA8E87A0049BFCA /* FirebaseAppCheck */ = { + isa = XCSwiftPackageProductDependency; + productName = FirebaseAppCheck; + }; + 8D589E2C2BA8EEA30049BFCA /* FirebaseAppCheck */ = { + isa = XCSwiftPackageProductDependency; + productName = FirebaseAppCheck; + }; + 8D589E2E2BA8EEBD0049BFCA /* FirebaseAppCheck */ = { + isa = XCSwiftPackageProductDependency; + productName = FirebaseAppCheck; + }; + 8D589E302BA8EED80049BFCA /* FirebaseAppCheck */ = { + isa = XCSwiftPackageProductDependency; + productName = FirebaseAppCheck; + }; + 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..0e3073d6da2 --- /dev/null +++ b/FirebaseVertexAI/Sample/GenerativeAITextSample/ViewModels/SummarizeViewModel.swift @@ -0,0 +1,71 @@ +// 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.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