From 92c36195354090cbe619a200f619681b77298485 Mon Sep 17 00:00:00 2001 From: Paul Beusterien Date: Thu, 23 Oct 2025 15:56:04 -0700 Subject: [PATCH 01/16] Build UI for template generate content --- .../project.pbxproj | 16 +++- .../FirebaseAIExample/ContentView.swift | 5 ++ .../GenerateContentFromTemplateScreen.swift | 89 +++++++++++++++++++ ...GenerateContentFromTemplateViewModel.swift | 71 +++++++++++++++ 4 files changed, 179 insertions(+), 2 deletions(-) create mode 100644 firebaseai/GenerativeAITextExample/Screens/GenerateContentFromTemplateScreen.swift create mode 100644 firebaseai/GenerativeAITextExample/ViewModels/GenerateContentFromTemplateViewModel.swift diff --git a/firebaseai/FirebaseAIExample.xcodeproj/project.pbxproj b/firebaseai/FirebaseAIExample.xcodeproj/project.pbxproj index bffe367de..f8dac26f1 100644 --- a/firebaseai/FirebaseAIExample.xcodeproj/project.pbxproj +++ b/firebaseai/FirebaseAIExample.xcodeproj/project.pbxproj @@ -60,6 +60,10 @@ AEE793DF2E256D3900708F02 /* GoogleSearchSuggestionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = AEE793DC2E256D3900708F02 /* GoogleSearchSuggestionView.swift */; }; AEE793E02E256D3900708F02 /* GroundedResponseView.swift in Sources */ = {isa = PBXBuildFile; fileRef = AEE793DD2E256D3900708F02 /* GroundedResponseView.swift */; }; DE26D95F2DBB3E9F007E6668 /* FirebaseAI in Frameworks */ = {isa = PBXBuildFile; productRef = DE26D95E2DBB3E9F007E6668 /* FirebaseAI */; }; + DE907A812EAAE53E00AE56CE /* GenerateContentFromTemplateScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = DE907A802EAAE53E00AE56CE /* GenerateContentFromTemplateScreen.swift */; }; + DE907A822EAAE53E00AE56CE /* GenerateContentFromTemplateScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = DE907A802EAAE53E00AE56CE /* GenerateContentFromTemplateScreen.swift */; }; + DE907A842EAAE55600AE56CE /* GenerateContentFromTemplateViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DE907A832EAAE55600AE56CE /* GenerateContentFromTemplateViewModel.swift */; }; + DE907A852EAAE55600AE56CE /* GenerateContentFromTemplateViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DE907A832EAAE55600AE56CE /* GenerateContentFromTemplateViewModel.swift */; }; DEFECAA92D7B4CCD00EF9621 /* ImagenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DEFECAA72D7B4CCD00EF9621 /* ImagenViewModel.swift */; }; DEFECAAA2D7B4CCD00EF9621 /* ImagenScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = DEFECAA62D7B4CCD00EF9621 /* ImagenScreen.swift */; }; /* End PBXBuildFile section */ @@ -94,6 +98,8 @@ 88E10F5C2B11135000C08E95 /* BouncingDots.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BouncingDots.swift; sourceTree = ""; }; AEE793DC2E256D3900708F02 /* GoogleSearchSuggestionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GoogleSearchSuggestionView.swift; sourceTree = ""; }; AEE793DD2E256D3900708F02 /* GroundedResponseView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroundedResponseView.swift; sourceTree = ""; }; + DE907A802EAAE53E00AE56CE /* GenerateContentFromTemplateScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GenerateContentFromTemplateScreen.swift; sourceTree = ""; }; + DE907A832EAAE55600AE56CE /* GenerateContentFromTemplateViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GenerateContentFromTemplateViewModel.swift; sourceTree = ""; }; DEFECAA62D7B4CCD00EF9621 /* ImagenScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImagenScreen.swift; sourceTree = ""; }; DEFECAA72D7B4CCD00EF9621 /* ImagenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImagenViewModel.swift; sourceTree = ""; }; /* End PBXFileReference section */ @@ -171,6 +177,7 @@ 88209C1A2B0FBDC300F64795 /* Screens */ = { isa = PBXGroup; children = ( + DE907A802EAAE53E00AE56CE /* GenerateContentFromTemplateScreen.swift */, 88209C1B2B0FBDC300F64795 /* GenerateContentScreen.swift */, ); path = Screens; @@ -179,6 +186,7 @@ 88209C1C2B0FBDC300F64795 /* ViewModels */ = { isa = PBXGroup; children = ( + DE907A832EAAE55600AE56CE /* GenerateContentFromTemplateViewModel.swift */, 88209C1D2B0FBDC300F64795 /* GenerateContentViewModel.swift */, ); path = ViewModels; @@ -468,6 +476,8 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + DE907A852EAAE55600AE56CE /* GenerateContentFromTemplateViewModel.swift in Sources */, + DE907A812EAAE53E00AE56CE /* GenerateContentFromTemplateScreen.swift in Sources */, 86BB55EA2E8B2D6D0054B8B5 /* FunctionCallingScreen.swift in Sources */, 86BB55EB2E8B2D6D0054B8B5 /* BouncingDots.swift in Sources */, 86BB55EC2E8B2D6D0054B8B5 /* FunctionCallingViewModel.swift in Sources */, @@ -494,6 +504,8 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + DE907A842EAAE55600AE56CE /* GenerateContentFromTemplateViewModel.swift in Sources */, + DE907A822EAAE53E00AE56CE /* GenerateContentFromTemplateScreen.swift in Sources */, 86C1F4832BC726150026816F /* FunctionCallingScreen.swift in Sources */, 886F95DF2B17D5010036F07A /* BouncingDots.swift in Sources */, 86C1F4842BC726150026816F /* FunctionCallingViewModel.swift in Sources */, @@ -827,8 +839,8 @@ isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/firebase/firebase-ios-sdk.git"; requirement = { - kind = upToNextMajorVersion; - minimumVersion = 12.0.0; + branch = "pb-spt"; + kind = branch; }; }; /* End XCRemoteSwiftPackageReference section */ diff --git a/firebaseai/FirebaseAIExample/ContentView.swift b/firebaseai/FirebaseAIExample/ContentView.swift index 1714a0653..f098d2b14 100644 --- a/firebaseai/FirebaseAIExample/ContentView.swift +++ b/firebaseai/FirebaseAIExample/ContentView.swift @@ -55,6 +55,11 @@ struct ContentView: View { } label: { Label("Generate Content", systemImage: "doc.text") } + NavigationLink { + GenerateContentFromTemplateScreen(firebaseService: firebaseService) + } label: { + Label("Generate Content from Template", systemImage: "doc.text.fill") + } NavigationLink { PhotoReasoningScreen(firebaseService: firebaseService) } label: { diff --git a/firebaseai/GenerativeAITextExample/Screens/GenerateContentFromTemplateScreen.swift b/firebaseai/GenerativeAITextExample/Screens/GenerateContentFromTemplateScreen.swift new file mode 100644 index 000000000..f35920b28 --- /dev/null +++ b/firebaseai/GenerativeAITextExample/Screens/GenerateContentFromTemplateScreen.swift @@ -0,0 +1,89 @@ +// 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 +#if canImport(FirebaseAILogic) + import FirebaseAILogic +#else + import FirebaseAI +#endif +import GenerativeAIUIComponents + +struct GenerateContentFromTemplateScreen: View { + let firebaseService: FirebaseAI + @StateObject var viewModel: GenerateContentFromTemplateViewModel + @State var userInput = "" + + init(firebaseService: FirebaseAI) { + self.firebaseService = firebaseService + _viewModel = + StateObject(wrappedValue: GenerateContentFromTemplateViewModel(firebaseService: firebaseService)) + } + + enum FocusedField: Hashable { + case message + } + + @FocusState + var focusedField: FocusedField? + + var body: some View { + VStack { + VStack(alignment: .leading) { + Text("Enter your name, then tap on _Go_ to run generateContent from template on it.") + .padding(.horizontal, 6) + InputField("Enter your name", text: $userInput) { + Text("Go") + } + .focused($focusedField, equals: .message) + .onSubmit { onGenerateContentTapped() } + } + .padding(.horizontal, 16) + + List { + HStack(alignment: .top) { + if viewModel.inProgress { + ProgressView() + } else { + Image(systemName: "cloud.circle.fill") + .font(.title2) + } + + Markdown("\(viewModel.outputText)") + } + .listRowSeparator(.hidden) + } + .listStyle(.plain) + } + .onTapGesture { + focusedField = nil + } + .navigationTitle("Template Text example") + } + + private func onGenerateContentTapped() { + focusedField = nil + + Task { + await viewModel.generateContentFromTemplate(name: userInput) + } + } +} + +#Preview { + NavigationStack { + GenerateContentFromTemplateScreen(firebaseService: FirebaseAI.firebaseAI()) + } +} diff --git a/firebaseai/GenerativeAITextExample/ViewModels/GenerateContentFromTemplateViewModel.swift b/firebaseai/GenerativeAITextExample/ViewModels/GenerateContentFromTemplateViewModel.swift new file mode 100644 index 000000000..eae036a94 --- /dev/null +++ b/firebaseai/GenerativeAITextExample/ViewModels/GenerateContentFromTemplateViewModel.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. + +#if canImport(FirebaseAILogic) + import FirebaseAILogic +#else + import FirebaseAI +#endif +import Foundation +import OSLog + +@MainActor +class GenerateContentFromTemplateViewModel: 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: TemplateGenerativeModel? + + init(firebaseService: FirebaseAI) { + model = firebaseService.templateGenerativeModel() + // model = firebaseService.generativeModel(modelName: "gemini-2.0-flash-001") + } + + func generateContentFromTemplate(name: String) async { + defer { + inProgress = false + } + guard let model else { + return + } + + do { + inProgress = true + errorMessage = nil + outputText = "" + + let response = try await model.generateContent( + templateID: "new-greeting", + inputs: [ + "name": name, + "language": "English", + ] + ) + if let text = response.text { + outputText = text + } + } catch { + logger.error("\(error.localizedDescription)") + errorMessage = error.localizedDescription + } + } +} From 4d507a379d5d7f77ea7525224f113e9a25692ceb Mon Sep 17 00:00:00 2001 From: Paul Beusterien Date: Thu, 23 Oct 2025 17:49:17 -0700 Subject: [PATCH 02/16] UI in place for 3 new examples --- .../project.pbxproj | 28 +++++++++++++++++-- .../FirebaseAIExample/ContentView.swift | 10 +++++++ ...GenerateContentFromTemplateViewModel.swift | 2 +- 3 files changed, 37 insertions(+), 3 deletions(-) diff --git a/firebaseai/FirebaseAIExample.xcodeproj/project.pbxproj b/firebaseai/FirebaseAIExample.xcodeproj/project.pbxproj index f8dac26f1..19f07b538 100644 --- a/firebaseai/FirebaseAIExample.xcodeproj/project.pbxproj +++ b/firebaseai/FirebaseAIExample.xcodeproj/project.pbxproj @@ -64,6 +64,14 @@ DE907A822EAAE53E00AE56CE /* GenerateContentFromTemplateScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = DE907A802EAAE53E00AE56CE /* GenerateContentFromTemplateScreen.swift */; }; DE907A842EAAE55600AE56CE /* GenerateContentFromTemplateViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DE907A832EAAE55600AE56CE /* GenerateContentFromTemplateViewModel.swift */; }; DE907A852EAAE55600AE56CE /* GenerateContentFromTemplateViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DE907A832EAAE55600AE56CE /* GenerateContentFromTemplateViewModel.swift */; }; + DE907A882EAAEBCC00AE56CE /* ImagenFromTemplateViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DE907A872EAAEBCC00AE56CE /* ImagenFromTemplateViewModel.swift */; }; + DE907A892EAAEBCC00AE56CE /* ImagenFromTemplateScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = DE907A862EAAEBCC00AE56CE /* ImagenFromTemplateScreen.swift */; }; + DE907A8A2EAAEBCC00AE56CE /* ImagenFromTemplateViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DE907A872EAAEBCC00AE56CE /* ImagenFromTemplateViewModel.swift */; }; + DE907A8B2EAAEBCC00AE56CE /* ImagenFromTemplateScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = DE907A862EAAEBCC00AE56CE /* ImagenFromTemplateScreen.swift */; }; + DE907A8D2EAAEE2300AE56CE /* ConversationFromTemplateViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DE907A8C2EAAEE2300AE56CE /* ConversationFromTemplateViewModel.swift */; }; + DE907A8E2EAAEE2300AE56CE /* ConversationFromTemplateViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DE907A8C2EAAEE2300AE56CE /* ConversationFromTemplateViewModel.swift */; }; + DE907A902EAAEE3E00AE56CE /* ConversationFromTemplateScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = DE907A8F2EAAEE3E00AE56CE /* ConversationFromTemplateScreen.swift */; }; + DE907A912EAAEE3E00AE56CE /* ConversationFromTemplateScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = DE907A8F2EAAEE3E00AE56CE /* ConversationFromTemplateScreen.swift */; }; DEFECAA92D7B4CCD00EF9621 /* ImagenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DEFECAA72D7B4CCD00EF9621 /* ImagenViewModel.swift */; }; DEFECAAA2D7B4CCD00EF9621 /* ImagenScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = DEFECAA62D7B4CCD00EF9621 /* ImagenScreen.swift */; }; /* End PBXBuildFile section */ @@ -100,6 +108,10 @@ AEE793DD2E256D3900708F02 /* GroundedResponseView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroundedResponseView.swift; sourceTree = ""; }; DE907A802EAAE53E00AE56CE /* GenerateContentFromTemplateScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GenerateContentFromTemplateScreen.swift; sourceTree = ""; }; DE907A832EAAE55600AE56CE /* GenerateContentFromTemplateViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GenerateContentFromTemplateViewModel.swift; sourceTree = ""; }; + DE907A862EAAEBCC00AE56CE /* ImagenFromTemplateScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImagenFromTemplateScreen.swift; sourceTree = ""; }; + DE907A872EAAEBCC00AE56CE /* ImagenFromTemplateViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImagenFromTemplateViewModel.swift; sourceTree = ""; }; + DE907A8C2EAAEE2300AE56CE /* ConversationFromTemplateViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConversationFromTemplateViewModel.swift; sourceTree = ""; }; + DE907A8F2EAAEE3E00AE56CE /* ConversationFromTemplateScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConversationFromTemplateScreen.swift; sourceTree = ""; }; DEFECAA62D7B4CCD00EF9621 /* ImagenScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImagenScreen.swift; sourceTree = ""; }; DEFECAA72D7B4CCD00EF9621 /* ImagenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImagenViewModel.swift; sourceTree = ""; }; /* End PBXFileReference section */ @@ -305,6 +317,7 @@ 88E10F502B11123600C08E95 /* ViewModels */ = { isa = PBXGroup; children = ( + DE907A8C2EAAEE2300AE56CE /* ConversationFromTemplateViewModel.swift */, 88E10F562B1112F600C08E95 /* ConversationViewModel.swift */, ); path = ViewModels; @@ -333,6 +346,7 @@ 88E10F532B1112B900C08E95 /* Screens */ = { isa = PBXGroup; children = ( + DE907A8F2EAAEE3E00AE56CE /* ConversationFromTemplateScreen.swift */, 88E10F542B1112CA00C08E95 /* ConversationScreen.swift */, ); path = Screens; @@ -350,6 +364,8 @@ DEFECAA82D7B4CCD00EF9621 /* ImagenScreen */ = { isa = PBXGroup; children = ( + DE907A862EAAEBCC00AE56CE /* ImagenFromTemplateScreen.swift */, + DE907A872EAAEBCC00AE56CE /* ImagenFromTemplateViewModel.swift */, DEFECAA62D7B4CCD00EF9621 /* ImagenScreen.swift */, DEFECAA72D7B4CCD00EF9621 /* ImagenViewModel.swift */, ); @@ -491,6 +507,10 @@ 86BB55F42E8B2D6D0054B8B5 /* PhotoReasoningScreen.swift in Sources */, 86BB55F52E8B2D6D0054B8B5 /* ImagenViewModel.swift in Sources */, 86BB55F62E8B2D6D0054B8B5 /* ImagenScreen.swift in Sources */, + DE907A902EAAEE3E00AE56CE /* ConversationFromTemplateScreen.swift in Sources */, + DE907A882EAAEBCC00AE56CE /* ImagenFromTemplateViewModel.swift in Sources */, + DE907A892EAAEBCC00AE56CE /* ImagenFromTemplateScreen.swift in Sources */, + DE907A8D2EAAEE2300AE56CE /* ConversationFromTemplateViewModel.swift in Sources */, 86BB55F72E8B2D6D0054B8B5 /* PhotoReasoningViewModel.swift in Sources */, 86BB55F82E8B2D6D0054B8B5 /* ConversationScreen.swift in Sources */, 86BB55F92E8B2D6D0054B8B5 /* ErrorView.swift in Sources */, @@ -519,6 +539,10 @@ 886F95DC2B17BAEF0036F07A /* PhotoReasoningScreen.swift in Sources */, DEFECAA92D7B4CCD00EF9621 /* ImagenViewModel.swift in Sources */, DEFECAAA2D7B4CCD00EF9621 /* ImagenScreen.swift in Sources */, + DE907A912EAAEE3E00AE56CE /* ConversationFromTemplateScreen.swift in Sources */, + DE907A8A2EAAEBCC00AE56CE /* ImagenFromTemplateViewModel.swift in Sources */, + DE907A8B2EAAEBCC00AE56CE /* ImagenFromTemplateScreen.swift in Sources */, + DE907A8E2EAAEE2300AE56CE /* ConversationFromTemplateViewModel.swift in Sources */, 886F95DB2B17BAEF0036F07A /* PhotoReasoningViewModel.swift in Sources */, 886F95E12B17D5010036F07A /* ConversationScreen.swift in Sources */, 88263BF02B239C09008AB09B /* ErrorView.swift in Sources */, @@ -732,7 +756,7 @@ "@executable_path/Frameworks", ); MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = com.google.firebase.quickstart.FirebaseAIExample; + PRODUCT_BUNDLE_IDENTIFIER = com.google.firebase.VertexAISample; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_VERSION = 5.0; @@ -762,7 +786,7 @@ "@executable_path/Frameworks", ); MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = com.google.firebase.quickstart.FirebaseAIExample; + PRODUCT_BUNDLE_IDENTIFIER = com.google.firebase.VertexAISample; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_VERSION = 5.0; diff --git a/firebaseai/FirebaseAIExample/ContentView.swift b/firebaseai/FirebaseAIExample/ContentView.swift index f098d2b14..1a1171139 100644 --- a/firebaseai/FirebaseAIExample/ContentView.swift +++ b/firebaseai/FirebaseAIExample/ContentView.swift @@ -70,6 +70,11 @@ struct ContentView: View { } label: { Label("Chat", systemImage: "ellipsis.message.fill") } + NavigationLink { + ConversationFromTemplateScreen(firebaseService: firebaseService, title: "Chat from Template") + } label: { + Label("Chat from Template", systemImage: "ellipsis.message") + } NavigationLink { ConversationScreen( firebaseService: firebaseService, @@ -89,6 +94,11 @@ struct ContentView: View { } label: { Label("Imagen", systemImage: "camera.circle") } + NavigationLink { + ImagenFromTemplateScreen(firebaseService: firebaseService) + } label: { + Label("Imagen from Template", systemImage: "camera.circle.fill") + } } } .navigationTitle("Generative AI Examples") diff --git a/firebaseai/GenerativeAITextExample/ViewModels/GenerateContentFromTemplateViewModel.swift b/firebaseai/GenerativeAITextExample/ViewModels/GenerateContentFromTemplateViewModel.swift index eae036a94..31979b86d 100644 --- a/firebaseai/GenerativeAITextExample/ViewModels/GenerateContentFromTemplateViewModel.swift +++ b/firebaseai/GenerativeAITextExample/ViewModels/GenerateContentFromTemplateViewModel.swift @@ -54,7 +54,7 @@ class GenerateContentFromTemplateViewModel: ObservableObject { outputText = "" let response = try await model.generateContent( - templateID: "new-greeting", + templateID: "apple-qs-greeting-u6mr", inputs: [ "name": name, "language": "English", From 3ed1f5c79a0c8e2a02c4b7d4637a6f59a010a99f Mon Sep 17 00:00:00 2001 From: Paul Beusterien Date: Fri, 24 Oct 2025 09:26:36 -0700 Subject: [PATCH 03/16] Two templates working --- .../Screens/GenerateContentFromTemplateScreen.swift | 2 +- .../ViewModels/GenerateContentFromTemplateViewModel.swift | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/firebaseai/GenerativeAITextExample/Screens/GenerateContentFromTemplateScreen.swift b/firebaseai/GenerativeAITextExample/Screens/GenerateContentFromTemplateScreen.swift index f35920b28..0b545f019 100644 --- a/firebaseai/GenerativeAITextExample/Screens/GenerateContentFromTemplateScreen.swift +++ b/firebaseai/GenerativeAITextExample/Screens/GenerateContentFromTemplateScreen.swift @@ -70,7 +70,7 @@ struct GenerateContentFromTemplateScreen: View { .onTapGesture { focusedField = nil } - .navigationTitle("Template Text example") + .navigationTitle("Template Generate Content") } private func onGenerateContentTapped() { diff --git a/firebaseai/GenerativeAITextExample/ViewModels/GenerateContentFromTemplateViewModel.swift b/firebaseai/GenerativeAITextExample/ViewModels/GenerateContentFromTemplateViewModel.swift index 31979b86d..2a79ebe90 100644 --- a/firebaseai/GenerativeAITextExample/ViewModels/GenerateContentFromTemplateViewModel.swift +++ b/firebaseai/GenerativeAITextExample/ViewModels/GenerateContentFromTemplateViewModel.swift @@ -54,10 +54,10 @@ class GenerateContentFromTemplateViewModel: ObservableObject { outputText = "" let response = try await model.generateContent( - templateID: "apple-qs-greeting-u6mr", + templateID: "apple-qs-greeting", inputs: [ "name": name, - "language": "English", + "language": "Spanish", ] ) if let text = response.text { From 1c5ed38697c78dbab3512d2cf4723ff7bf863ee9 Mon Sep 17 00:00:00 2001 From: Paul Beusterien Date: Fri, 24 Oct 2025 09:28:48 -0700 Subject: [PATCH 04/16] missing files --- .../ImagenFromTemplateScreen.swift | 99 +++++++++++++++++++ .../ImagenFromTemplateViewModel.swift | 80 +++++++++++++++ 2 files changed, 179 insertions(+) create mode 100644 firebaseai/ImagenScreen/ImagenFromTemplateScreen.swift create mode 100644 firebaseai/ImagenScreen/ImagenFromTemplateViewModel.swift diff --git a/firebaseai/ImagenScreen/ImagenFromTemplateScreen.swift b/firebaseai/ImagenScreen/ImagenFromTemplateScreen.swift new file mode 100644 index 000000000..b624f8a53 --- /dev/null +++ b/firebaseai/ImagenScreen/ImagenFromTemplateScreen.swift @@ -0,0 +1,99 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import SwiftUI +import GenerativeAIUIComponents +#if canImport(FirebaseAILogic) + import FirebaseAILogic +#else + import FirebaseAI +#endif + +struct ImagenFromTemplateScreen: View { + let firebaseService: FirebaseAI + @StateObject var viewModel: ImagenFromTemplateViewModel + + init(firebaseService: FirebaseAI) { + self.firebaseService = firebaseService + _viewModel = StateObject(wrappedValue: ImagenFromTemplateViewModel(firebaseService: firebaseService)) + } + + enum FocusedField: Hashable { + case message + } + + @FocusState + var focusedField: FocusedField? + + var body: some View { + ZStack { + ScrollView { + VStack { + InputField("Enter a prompt to generate an image from template", text: $viewModel.userInput) { + Image( + systemName: viewModel.inProgress ? "stop.circle.fill" : "paperplane.circle.fill" + ) + .font(.title) + } + .focused($focusedField, equals: .message) + .onSubmit { sendOrStop() } + + let spacing: CGFloat = 10 + LazyVGrid(columns: [ + GridItem(.flexible(), spacing: spacing), + GridItem(.flexible(), spacing: spacing), + ], spacing: spacing) { + ForEach(viewModel.images, id: \.self) { image in + Image(uiImage: image) + .resizable() + .aspectRatio(1, contentMode: .fill) + .cornerRadius(12) + .clipped() + } + } + .padding(.horizontal, spacing) + } + } + if viewModel.inProgress { + ProgressOverlay() + } + } + .onTapGesture { + focusedField = nil + } + .navigationTitle("Imagen Template") + .onAppear { + focusedField = .message + } + } + + private func sendMessage() { + Task { + await viewModel.generateImageFromTemplate(prompt: viewModel.userInput) + focusedField = .message + } + } + + private func sendOrStop() { + if viewModel.inProgress { + viewModel.stop() + } else { + sendMessage() + } + } +} + +#Preview { + ImagenFromTemplateScreen(firebaseService: FirebaseAI.firebaseAI()) +} diff --git a/firebaseai/ImagenScreen/ImagenFromTemplateViewModel.swift b/firebaseai/ImagenScreen/ImagenFromTemplateViewModel.swift new file mode 100644 index 000000000..976875536 --- /dev/null +++ b/firebaseai/ImagenScreen/ImagenFromTemplateViewModel.swift @@ -0,0 +1,80 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#if canImport(FirebaseAILogic) + import FirebaseAILogic +#else + import FirebaseAI +#endif +import Foundation +import OSLog +import SwiftUI + +@MainActor +class ImagenFromTemplateViewModel: ObservableObject { + private var logger = Logger(subsystem: Bundle.main.bundleIdentifier!, category: "generative-ai") + + @Published + var userInput: String = "" + + @Published + var images = [UIImage]() + + @Published + var errorMessage: String? + + @Published + var inProgress = false + + private let model: TemplateImagenModel + + private var generateImagesTask: Task? + + init(firebaseService: FirebaseAI) { + model = firebaseService.templateImagenModel() + } + + func generateImageFromTemplate(prompt: String) async { + stop() + + generateImagesTask = Task { + inProgress = true + defer { + inProgress = false + } + + do { + let response = try await model.generateImages( + templateID: "image-generation-basic", + inputs: [ + "prompt": prompt, + ] + ) + + if !Task.isCancelled { + images = response.images.compactMap { UIImage(data: $0.data) } + } + } catch { + if !Task.isCancelled { + logger.error("Error generating images from template: \(error)") + } + } + } + } + + func stop() { + generateImagesTask?.cancel() + generateImagesTask = nil + } +} From bfacc00e6fd516d7ec6061fae8001a5b11d41070 Mon Sep 17 00:00:00 2001 From: Paul Beusterien Date: Fri, 24 Oct 2025 09:46:11 -0700 Subject: [PATCH 05/16] style --- firebaseai/FirebaseAIExample/ContentView.swift | 5 ++++- .../Screens/GenerateContentFromTemplateScreen.swift | 4 +++- .../ViewModels/GenerateContentFromTemplateViewModel.swift | 2 +- firebaseai/ImagenScreen/ImagenFromTemplateScreen.swift | 8 ++++++-- 4 files changed, 14 insertions(+), 5 deletions(-) diff --git a/firebaseai/FirebaseAIExample/ContentView.swift b/firebaseai/FirebaseAIExample/ContentView.swift index 1a1171139..850ff917f 100644 --- a/firebaseai/FirebaseAIExample/ContentView.swift +++ b/firebaseai/FirebaseAIExample/ContentView.swift @@ -71,7 +71,10 @@ struct ContentView: View { Label("Chat", systemImage: "ellipsis.message.fill") } NavigationLink { - ConversationFromTemplateScreen(firebaseService: firebaseService, title: "Chat from Template") + ConversationFromTemplateScreen( + firebaseService: firebaseService, + title: "Chat from Template" + ) } label: { Label("Chat from Template", systemImage: "ellipsis.message") } diff --git a/firebaseai/GenerativeAITextExample/Screens/GenerateContentFromTemplateScreen.swift b/firebaseai/GenerativeAITextExample/Screens/GenerateContentFromTemplateScreen.swift index 0b545f019..80023d5ad 100644 --- a/firebaseai/GenerativeAITextExample/Screens/GenerateContentFromTemplateScreen.swift +++ b/firebaseai/GenerativeAITextExample/Screens/GenerateContentFromTemplateScreen.swift @@ -29,7 +29,9 @@ struct GenerateContentFromTemplateScreen: View { init(firebaseService: FirebaseAI) { self.firebaseService = firebaseService _viewModel = - StateObject(wrappedValue: GenerateContentFromTemplateViewModel(firebaseService: firebaseService)) + StateObject( + wrappedValue: GenerateContentFromTemplateViewModel(firebaseService: firebaseService) + ) } enum FocusedField: Hashable { diff --git a/firebaseai/GenerativeAITextExample/ViewModels/GenerateContentFromTemplateViewModel.swift b/firebaseai/GenerativeAITextExample/ViewModels/GenerateContentFromTemplateViewModel.swift index 2a79ebe90..5a5097ffe 100644 --- a/firebaseai/GenerativeAITextExample/ViewModels/GenerateContentFromTemplateViewModel.swift +++ b/firebaseai/GenerativeAITextExample/ViewModels/GenerateContentFromTemplateViewModel.swift @@ -37,7 +37,7 @@ class GenerateContentFromTemplateViewModel: ObservableObject { init(firebaseService: FirebaseAI) { model = firebaseService.templateGenerativeModel() - // model = firebaseService.generativeModel(modelName: "gemini-2.0-flash-001") + // model = firebaseService.generativeModel(modelName: "gemini-2.0-flash-001") } func generateContentFromTemplate(name: String) async { diff --git a/firebaseai/ImagenScreen/ImagenFromTemplateScreen.swift b/firebaseai/ImagenScreen/ImagenFromTemplateScreen.swift index b624f8a53..8f2df7d27 100644 --- a/firebaseai/ImagenScreen/ImagenFromTemplateScreen.swift +++ b/firebaseai/ImagenScreen/ImagenFromTemplateScreen.swift @@ -26,7 +26,8 @@ struct ImagenFromTemplateScreen: View { init(firebaseService: FirebaseAI) { self.firebaseService = firebaseService - _viewModel = StateObject(wrappedValue: ImagenFromTemplateViewModel(firebaseService: firebaseService)) + _viewModel = + StateObject(wrappedValue: ImagenFromTemplateViewModel(firebaseService: firebaseService)) } enum FocusedField: Hashable { @@ -40,7 +41,10 @@ struct ImagenFromTemplateScreen: View { ZStack { ScrollView { VStack { - InputField("Enter a prompt to generate an image from template", text: $viewModel.userInput) { + InputField( + "Enter a prompt to generate an image from template", + text: $viewModel.userInput + ) { Image( systemName: viewModel.inProgress ? "stop.circle.fill" : "paperplane.circle.fill" ) From 1a15495166c66ba9051a2e57a2d08c030416bde6 Mon Sep 17 00:00:00 2001 From: Paul Beusterien Date: Fri, 24 Oct 2025 10:13:59 -0700 Subject: [PATCH 06/16] Missing files --- .../ConversationFromTemplateScreen.swift | 150 ++++++++++++++++++ .../ConversationFromTemplateViewModel.swift | 96 +++++++++++ 2 files changed, 246 insertions(+) create mode 100644 firebaseai/ChatExample/Screens/ConversationFromTemplateScreen.swift create mode 100644 firebaseai/ChatExample/ViewModels/ConversationFromTemplateViewModel.swift diff --git a/firebaseai/ChatExample/Screens/ConversationFromTemplateScreen.swift b/firebaseai/ChatExample/Screens/ConversationFromTemplateScreen.swift new file mode 100644 index 000000000..4adb1523d --- /dev/null +++ b/firebaseai/ChatExample/Screens/ConversationFromTemplateScreen.swift @@ -0,0 +1,150 @@ +// 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. + +#if canImport(FirebaseAILogic) + import FirebaseAILogic +#else + import FirebaseAI +#endif +import GenerativeAIUIComponents +import SwiftUI + +struct ConversationFromTemplateScreen: View { + let firebaseService: FirebaseAI + let title: String + @StateObject var viewModel: ConversationFromTemplateViewModel + + @State + private var userPrompt = "" + + init(firebaseService: FirebaseAI, title: String) { + self.title = title + self.firebaseService = firebaseService + _viewModel = + StateObject(wrappedValue: ConversationFromTemplateViewModel(firebaseService: firebaseService)) + } + + 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() } + } + .onTapGesture { + focusedField = nil + } + .toolbar { + ToolbarItem(placement: .primaryAction) { + Button(action: newChat) { + Image(systemName: "square.and.pencil") + } + } + } + .navigationTitle(title) + .onAppear { + focusedField = .message + } + } + + private func sendMessage() { + Task { + let prompt = userPrompt + userPrompt = "" + await viewModel.sendMessage(prompt) + } + } + + private func sendOrStop() { + focusedField = nil + + if viewModel.busy { + viewModel.stop() + } else { + sendMessage() + } + } + + private func newChat() { + viewModel.startNewChat() + } +} + +struct ConversationFromTemplateScreen_Previews: PreviewProvider { + struct ContainerView: View { + @StateObject var viewModel = ConversationFromTemplateViewModel(firebaseService: FirebaseAI + .firebaseAI()) // Example service init + + var body: some View { + ConversationFromTemplateScreen( + firebaseService: FirebaseAI.firebaseAI(), + title: "Chat from Template sample" + ) + .onAppear { + viewModel.messages = ChatMessage.samples + } + } + } + + static var previews: some View { + NavigationStack { + ConversationFromTemplateScreen( + firebaseService: FirebaseAI.firebaseAI(), + title: "Chat from Template sample" + ) + } + } +} diff --git a/firebaseai/ChatExample/ViewModels/ConversationFromTemplateViewModel.swift b/firebaseai/ChatExample/ViewModels/ConversationFromTemplateViewModel.swift new file mode 100644 index 000000000..63d19d28f --- /dev/null +++ b/firebaseai/ChatExample/ViewModels/ConversationFromTemplateViewModel.swift @@ -0,0 +1,96 @@ +// 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. + +#if canImport(FirebaseAILogic) + import FirebaseAILogic +#else + import FirebaseAI +#endif +import Foundation +import UIKit + +@MainActor +class ConversationFromTemplateViewModel: 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: TemplateGenerativeModel + private var chat: TemplateChatSession + private var stopGenerating = false + + private var chatTask: Task? + + init(firebaseService: FirebaseAI) { + model = firebaseService.templateGenerativeModel() + chat = model.startChat(templateID: "chat-history") + } + + func sendMessage(_ text: String) async { + error = nil + await internalSendMessage(text) + } + + func startNewChat() { + stop() + error = nil + chat = model.startChat(templateID: "chat-history") + messages.removeAll() + } + + func stop() { + chatTask?.cancel() + error = nil + } + + 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 { + let response = try await chat.sendMessage(text, inputs: ["message": 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() + } + } + } +} From 4557a059783705c053fd719c7855ef4fb4f29d3c Mon Sep 17 00:00:00 2001 From: Andrew Heard Date: Fri, 24 Oct 2025 15:50:42 -0400 Subject: [PATCH 07/16] [Firebase AI] Add parameters to SPT Chat sample (#1793) --- .../ConversationFromTemplateScreen.swift | 19 +++++++- .../ConversationFromTemplateViewModel.swift | 43 ++++++++++++++++--- 2 files changed, 55 insertions(+), 7 deletions(-) diff --git a/firebaseai/ChatExample/Screens/ConversationFromTemplateScreen.swift b/firebaseai/ChatExample/Screens/ConversationFromTemplateScreen.swift index 4adb1523d..fbd49ff48 100644 --- a/firebaseai/ChatExample/Screens/ConversationFromTemplateScreen.swift +++ b/firebaseai/ChatExample/Screens/ConversationFromTemplateScreen.swift @@ -28,6 +28,12 @@ struct ConversationFromTemplateScreen: View { @State private var userPrompt = "" + @State + private var userName = "" + + @State + private var preferredLanguage = "" + init(firebaseService: FirebaseAI, title: String) { self.title = title self.firebaseService = firebaseService @@ -44,6 +50,17 @@ struct ConversationFromTemplateScreen: View { var body: some View { VStack { + VStack { + HStack { + Text("Name:") + TextField("Your name", text: $userName) + } + HStack { + Text("Language:") + TextField("Your preferred response language", text: $preferredLanguage) + } + }.padding() + ScrollViewReader { scrollViewProxy in List { ForEach(viewModel.messages) { message in @@ -104,7 +121,7 @@ struct ConversationFromTemplateScreen: View { Task { let prompt = userPrompt userPrompt = "" - await viewModel.sendMessage(prompt) + await viewModel.sendMessage(prompt, name: userName, language: preferredLanguage) } } diff --git a/firebaseai/ChatExample/ViewModels/ConversationFromTemplateViewModel.swift b/firebaseai/ChatExample/ViewModels/ConversationFromTemplateViewModel.swift index 63d19d28f..7f060aa57 100644 --- a/firebaseai/ChatExample/ViewModels/ConversationFromTemplateViewModel.swift +++ b/firebaseai/ChatExample/ViewModels/ConversationFromTemplateViewModel.swift @@ -20,6 +20,28 @@ import Foundation import UIKit +// Template Details +// +// Configuration +// +// input: +// default: +// language: "English" +// schema: +// name?: string +// language?: string +// message: string +// +// Prompt and system instructions +// +// {{role "system"}} +// {{#if name}}The user's name is {{name}}.{{/if}} +// The user prefers to communicate in {{language}}. +// {{history}} +// {{role "user"}} +// {{message}} +// + @MainActor class ConversationFromTemplateViewModel: ObservableObject { /// This array holds both the user's and the system's chat messages @@ -41,18 +63,20 @@ class ConversationFromTemplateViewModel: ObservableObject { init(firebaseService: FirebaseAI) { model = firebaseService.templateGenerativeModel() - chat = model.startChat(templateID: "chat-history") + chat = model.startChat(templateID: "apple-qs-chat") } - func sendMessage(_ text: String) async { + func sendMessage(_ text: String, name: String, language: String) async { error = nil - await internalSendMessage(text) + let name = name.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty ? nil : name + let language = language.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty ? nil : language + await internalSendMessage(text, name: name, language: language) } func startNewChat() { stop() error = nil - chat = model.startChat(templateID: "chat-history") + chat = model.startChat(templateID: "apple-qs-chat") messages.removeAll() } @@ -61,7 +85,7 @@ class ConversationFromTemplateViewModel: ObservableObject { error = nil } - private func internalSendMessage(_ text: String) async { + private func internalSendMessage(_ text: String, name: String?, language: String?) async { chatTask?.cancel() chatTask = Task { @@ -79,7 +103,14 @@ class ConversationFromTemplateViewModel: ObservableObject { messages.append(systemMessage) do { - let response = try await chat.sendMessage(text, inputs: ["message": text]) + var inputs = ["message": text] + if let name { + inputs["name"] = name + } + if let language { + inputs["language"] = language + } + let response = try await chat.sendMessage(text, inputs: inputs) if let responseText = response.text { // replace pending message with backend response From 940cabf7a5a12c67bafa00eaf9c15f8bf786e255 Mon Sep 17 00:00:00 2001 From: Paul Beusterien Date: Sat, 25 Oct 2025 08:23:50 -0700 Subject: [PATCH 08/16] Add template details to comments --- ...GenerateContentFromTemplateViewModel.swift | 20 ++++++++++++++++++- .../ImagenFromTemplateViewModel.swift | 13 ++++++++++++ 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/firebaseai/GenerativeAITextExample/ViewModels/GenerateContentFromTemplateViewModel.swift b/firebaseai/GenerativeAITextExample/ViewModels/GenerateContentFromTemplateViewModel.swift index 5a5097ffe..96a7cec5c 100644 --- a/firebaseai/GenerativeAITextExample/ViewModels/GenerateContentFromTemplateViewModel.swift +++ b/firebaseai/GenerativeAITextExample/ViewModels/GenerateContentFromTemplateViewModel.swift @@ -20,6 +20,25 @@ import Foundation import OSLog +// Template Details +// +// Configuration +// +// input: +// default: +// language: english +// schema: +// name: string +// language?: string +// +// Prompt and system instructions +// +// {{role "system"}} +// The user's name is {{name}}. They prefer to communicate in {{language}}. +// {{role "user"}} +// Say hello. +// + @MainActor class GenerateContentFromTemplateViewModel: ObservableObject { private var logger = Logger(subsystem: Bundle.main.bundleIdentifier!, category: "generative-ai") @@ -37,7 +56,6 @@ class GenerateContentFromTemplateViewModel: ObservableObject { init(firebaseService: FirebaseAI) { model = firebaseService.templateGenerativeModel() - // model = firebaseService.generativeModel(modelName: "gemini-2.0-flash-001") } func generateContentFromTemplate(name: String) async { diff --git a/firebaseai/ImagenScreen/ImagenFromTemplateViewModel.swift b/firebaseai/ImagenScreen/ImagenFromTemplateViewModel.swift index 976875536..02bb15bfc 100644 --- a/firebaseai/ImagenScreen/ImagenFromTemplateViewModel.swift +++ b/firebaseai/ImagenScreen/ImagenFromTemplateViewModel.swift @@ -21,6 +21,19 @@ import Foundation import OSLog import SwiftUI +// Template Details +// +// Configuration +// +// input: +// schema: +// prompt: 'string' +// +// Prompt and system instructions +// +// Create an image containing {{prompt}} +// + @MainActor class ImagenFromTemplateViewModel: ObservableObject { private var logger = Logger(subsystem: Bundle.main.bundleIdentifier!, category: "generative-ai") From d7c2e89efc4542f44af121652cf12d67477ac7ca Mon Sep 17 00:00:00 2001 From: Paul Beusterien Date: Fri, 31 Oct 2025 13:57:20 -0700 Subject: [PATCH 09/16] Remove template chat for now --- .../ConversationFromTemplateScreen.swift | 167 ------------------ .../ConversationFromTemplateViewModel.swift | 127 ------------- .../project.pbxproj | 12 -- .../FirebaseAIExample/ContentView.swift | 8 - 4 files changed, 314 deletions(-) delete mode 100644 firebaseai/ChatExample/Screens/ConversationFromTemplateScreen.swift delete mode 100644 firebaseai/ChatExample/ViewModels/ConversationFromTemplateViewModel.swift diff --git a/firebaseai/ChatExample/Screens/ConversationFromTemplateScreen.swift b/firebaseai/ChatExample/Screens/ConversationFromTemplateScreen.swift deleted file mode 100644 index fbd49ff48..000000000 --- a/firebaseai/ChatExample/Screens/ConversationFromTemplateScreen.swift +++ /dev/null @@ -1,167 +0,0 @@ -// 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. - -#if canImport(FirebaseAILogic) - import FirebaseAILogic -#else - import FirebaseAI -#endif -import GenerativeAIUIComponents -import SwiftUI - -struct ConversationFromTemplateScreen: View { - let firebaseService: FirebaseAI - let title: String - @StateObject var viewModel: ConversationFromTemplateViewModel - - @State - private var userPrompt = "" - - @State - private var userName = "" - - @State - private var preferredLanguage = "" - - init(firebaseService: FirebaseAI, title: String) { - self.title = title - self.firebaseService = firebaseService - _viewModel = - StateObject(wrappedValue: ConversationFromTemplateViewModel(firebaseService: firebaseService)) - } - - enum FocusedField: Hashable { - case message - } - - @FocusState - var focusedField: FocusedField? - - var body: some View { - VStack { - VStack { - HStack { - Text("Name:") - TextField("Your name", text: $userName) - } - HStack { - Text("Language:") - TextField("Your preferred response language", text: $preferredLanguage) - } - }.padding() - - 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() } - } - .onTapGesture { - focusedField = nil - } - .toolbar { - ToolbarItem(placement: .primaryAction) { - Button(action: newChat) { - Image(systemName: "square.and.pencil") - } - } - } - .navigationTitle(title) - .onAppear { - focusedField = .message - } - } - - private func sendMessage() { - Task { - let prompt = userPrompt - userPrompt = "" - await viewModel.sendMessage(prompt, name: userName, language: preferredLanguage) - } - } - - private func sendOrStop() { - focusedField = nil - - if viewModel.busy { - viewModel.stop() - } else { - sendMessage() - } - } - - private func newChat() { - viewModel.startNewChat() - } -} - -struct ConversationFromTemplateScreen_Previews: PreviewProvider { - struct ContainerView: View { - @StateObject var viewModel = ConversationFromTemplateViewModel(firebaseService: FirebaseAI - .firebaseAI()) // Example service init - - var body: some View { - ConversationFromTemplateScreen( - firebaseService: FirebaseAI.firebaseAI(), - title: "Chat from Template sample" - ) - .onAppear { - viewModel.messages = ChatMessage.samples - } - } - } - - static var previews: some View { - NavigationStack { - ConversationFromTemplateScreen( - firebaseService: FirebaseAI.firebaseAI(), - title: "Chat from Template sample" - ) - } - } -} diff --git a/firebaseai/ChatExample/ViewModels/ConversationFromTemplateViewModel.swift b/firebaseai/ChatExample/ViewModels/ConversationFromTemplateViewModel.swift deleted file mode 100644 index 7f060aa57..000000000 --- a/firebaseai/ChatExample/ViewModels/ConversationFromTemplateViewModel.swift +++ /dev/null @@ -1,127 +0,0 @@ -// 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. - -#if canImport(FirebaseAILogic) - import FirebaseAILogic -#else - import FirebaseAI -#endif -import Foundation -import UIKit - -// Template Details -// -// Configuration -// -// input: -// default: -// language: "English" -// schema: -// name?: string -// language?: string -// message: string -// -// Prompt and system instructions -// -// {{role "system"}} -// {{#if name}}The user's name is {{name}}.{{/if}} -// The user prefers to communicate in {{language}}. -// {{history}} -// {{role "user"}} -// {{message}} -// - -@MainActor -class ConversationFromTemplateViewModel: 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: TemplateGenerativeModel - private var chat: TemplateChatSession - private var stopGenerating = false - - private var chatTask: Task? - - init(firebaseService: FirebaseAI) { - model = firebaseService.templateGenerativeModel() - chat = model.startChat(templateID: "apple-qs-chat") - } - - func sendMessage(_ text: String, name: String, language: String) async { - error = nil - let name = name.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty ? nil : name - let language = language.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty ? nil : language - await internalSendMessage(text, name: name, language: language) - } - - func startNewChat() { - stop() - error = nil - chat = model.startChat(templateID: "apple-qs-chat") - messages.removeAll() - } - - func stop() { - chatTask?.cancel() - error = nil - } - - private func internalSendMessage(_ text: String, name: String?, language: 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 inputs = ["message": text] - if let name { - inputs["name"] = name - } - if let language { - inputs["language"] = language - } - let response = try await chat.sendMessage(text, inputs: inputs) - - 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/firebaseai/FirebaseAIExample.xcodeproj/project.pbxproj b/firebaseai/FirebaseAIExample.xcodeproj/project.pbxproj index 19f07b538..4d557da85 100644 --- a/firebaseai/FirebaseAIExample.xcodeproj/project.pbxproj +++ b/firebaseai/FirebaseAIExample.xcodeproj/project.pbxproj @@ -68,10 +68,6 @@ DE907A892EAAEBCC00AE56CE /* ImagenFromTemplateScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = DE907A862EAAEBCC00AE56CE /* ImagenFromTemplateScreen.swift */; }; DE907A8A2EAAEBCC00AE56CE /* ImagenFromTemplateViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DE907A872EAAEBCC00AE56CE /* ImagenFromTemplateViewModel.swift */; }; DE907A8B2EAAEBCC00AE56CE /* ImagenFromTemplateScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = DE907A862EAAEBCC00AE56CE /* ImagenFromTemplateScreen.swift */; }; - DE907A8D2EAAEE2300AE56CE /* ConversationFromTemplateViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DE907A8C2EAAEE2300AE56CE /* ConversationFromTemplateViewModel.swift */; }; - DE907A8E2EAAEE2300AE56CE /* ConversationFromTemplateViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DE907A8C2EAAEE2300AE56CE /* ConversationFromTemplateViewModel.swift */; }; - DE907A902EAAEE3E00AE56CE /* ConversationFromTemplateScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = DE907A8F2EAAEE3E00AE56CE /* ConversationFromTemplateScreen.swift */; }; - DE907A912EAAEE3E00AE56CE /* ConversationFromTemplateScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = DE907A8F2EAAEE3E00AE56CE /* ConversationFromTemplateScreen.swift */; }; DEFECAA92D7B4CCD00EF9621 /* ImagenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DEFECAA72D7B4CCD00EF9621 /* ImagenViewModel.swift */; }; DEFECAAA2D7B4CCD00EF9621 /* ImagenScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = DEFECAA62D7B4CCD00EF9621 /* ImagenScreen.swift */; }; /* End PBXBuildFile section */ @@ -110,8 +106,6 @@ DE907A832EAAE55600AE56CE /* GenerateContentFromTemplateViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GenerateContentFromTemplateViewModel.swift; sourceTree = ""; }; DE907A862EAAEBCC00AE56CE /* ImagenFromTemplateScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImagenFromTemplateScreen.swift; sourceTree = ""; }; DE907A872EAAEBCC00AE56CE /* ImagenFromTemplateViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImagenFromTemplateViewModel.swift; sourceTree = ""; }; - DE907A8C2EAAEE2300AE56CE /* ConversationFromTemplateViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConversationFromTemplateViewModel.swift; sourceTree = ""; }; - DE907A8F2EAAEE3E00AE56CE /* ConversationFromTemplateScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConversationFromTemplateScreen.swift; sourceTree = ""; }; DEFECAA62D7B4CCD00EF9621 /* ImagenScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImagenScreen.swift; sourceTree = ""; }; DEFECAA72D7B4CCD00EF9621 /* ImagenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImagenViewModel.swift; sourceTree = ""; }; /* End PBXFileReference section */ @@ -317,7 +311,6 @@ 88E10F502B11123600C08E95 /* ViewModels */ = { isa = PBXGroup; children = ( - DE907A8C2EAAEE2300AE56CE /* ConversationFromTemplateViewModel.swift */, 88E10F562B1112F600C08E95 /* ConversationViewModel.swift */, ); path = ViewModels; @@ -346,7 +339,6 @@ 88E10F532B1112B900C08E95 /* Screens */ = { isa = PBXGroup; children = ( - DE907A8F2EAAEE3E00AE56CE /* ConversationFromTemplateScreen.swift */, 88E10F542B1112CA00C08E95 /* ConversationScreen.swift */, ); path = Screens; @@ -507,10 +499,8 @@ 86BB55F42E8B2D6D0054B8B5 /* PhotoReasoningScreen.swift in Sources */, 86BB55F52E8B2D6D0054B8B5 /* ImagenViewModel.swift in Sources */, 86BB55F62E8B2D6D0054B8B5 /* ImagenScreen.swift in Sources */, - DE907A902EAAEE3E00AE56CE /* ConversationFromTemplateScreen.swift in Sources */, DE907A882EAAEBCC00AE56CE /* ImagenFromTemplateViewModel.swift in Sources */, DE907A892EAAEBCC00AE56CE /* ImagenFromTemplateScreen.swift in Sources */, - DE907A8D2EAAEE2300AE56CE /* ConversationFromTemplateViewModel.swift in Sources */, 86BB55F72E8B2D6D0054B8B5 /* PhotoReasoningViewModel.swift in Sources */, 86BB55F82E8B2D6D0054B8B5 /* ConversationScreen.swift in Sources */, 86BB55F92E8B2D6D0054B8B5 /* ErrorView.swift in Sources */, @@ -539,10 +529,8 @@ 886F95DC2B17BAEF0036F07A /* PhotoReasoningScreen.swift in Sources */, DEFECAA92D7B4CCD00EF9621 /* ImagenViewModel.swift in Sources */, DEFECAAA2D7B4CCD00EF9621 /* ImagenScreen.swift in Sources */, - DE907A912EAAEE3E00AE56CE /* ConversationFromTemplateScreen.swift in Sources */, DE907A8A2EAAEBCC00AE56CE /* ImagenFromTemplateViewModel.swift in Sources */, DE907A8B2EAAEBCC00AE56CE /* ImagenFromTemplateScreen.swift in Sources */, - DE907A8E2EAAEE2300AE56CE /* ConversationFromTemplateViewModel.swift in Sources */, 886F95DB2B17BAEF0036F07A /* PhotoReasoningViewModel.swift in Sources */, 886F95E12B17D5010036F07A /* ConversationScreen.swift in Sources */, 88263BF02B239C09008AB09B /* ErrorView.swift in Sources */, diff --git a/firebaseai/FirebaseAIExample/ContentView.swift b/firebaseai/FirebaseAIExample/ContentView.swift index 850ff917f..73bc64fcf 100644 --- a/firebaseai/FirebaseAIExample/ContentView.swift +++ b/firebaseai/FirebaseAIExample/ContentView.swift @@ -70,14 +70,6 @@ struct ContentView: View { } label: { Label("Chat", systemImage: "ellipsis.message.fill") } - NavigationLink { - ConversationFromTemplateScreen( - firebaseService: firebaseService, - title: "Chat from Template" - ) - } label: { - Label("Chat from Template", systemImage: "ellipsis.message") - } NavigationLink { ConversationScreen( firebaseService: firebaseService, From 143768453b1159f8db3d272b5a07cdf533ede33f Mon Sep 17 00:00:00 2001 From: Paul Beusterien Date: Fri, 31 Oct 2025 16:52:30 -0700 Subject: [PATCH 10/16] Update to main Firebase branch --- firebaseai/FirebaseAIExample.xcodeproj/project.pbxproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/firebaseai/FirebaseAIExample.xcodeproj/project.pbxproj b/firebaseai/FirebaseAIExample.xcodeproj/project.pbxproj index 4d557da85..8087de5ad 100644 --- a/firebaseai/FirebaseAIExample.xcodeproj/project.pbxproj +++ b/firebaseai/FirebaseAIExample.xcodeproj/project.pbxproj @@ -851,7 +851,7 @@ isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/firebase/firebase-ios-sdk.git"; requirement = { - branch = "pb-spt"; + branch = main; kind = branch; }; }; From 48fcc88651e22a342d6e29a9950cbb4fa233804f Mon Sep 17 00:00:00 2001 From: Paul Beusterien Date: Thu, 13 Nov 2025 16:17:32 -0800 Subject: [PATCH 11/16] Update now that 12.6.0 has released --- firebaseai/FirebaseAIExample.xcodeproj/project.pbxproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/firebaseai/FirebaseAIExample.xcodeproj/project.pbxproj b/firebaseai/FirebaseAIExample.xcodeproj/project.pbxproj index 8087de5ad..8ba645085 100644 --- a/firebaseai/FirebaseAIExample.xcodeproj/project.pbxproj +++ b/firebaseai/FirebaseAIExample.xcodeproj/project.pbxproj @@ -851,8 +851,8 @@ isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/firebase/firebase-ios-sdk.git"; requirement = { - branch = main; - kind = branch; + kind = upToNextMajorVersion; + minimumVersion = 12.6.0; }; }; /* End XCRemoteSwiftPackageReference section */ From 24ac0c43100a13789a8df7338be1617852f88102 Mon Sep 17 00:00:00 2001 From: Paul Beusterien Date: Thu, 13 Nov 2025 16:19:23 -0800 Subject: [PATCH 12/16] Update firebaseai/GenerativeAITextExample/ViewModels/GenerateContentFromTemplateViewModel.swift Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --- .../ViewModels/GenerateContentFromTemplateViewModel.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/firebaseai/GenerativeAITextExample/ViewModels/GenerateContentFromTemplateViewModel.swift b/firebaseai/GenerativeAITextExample/ViewModels/GenerateContentFromTemplateViewModel.swift index 96a7cec5c..d9d727b65 100644 --- a/firebaseai/GenerativeAITextExample/ViewModels/GenerateContentFromTemplateViewModel.swift +++ b/firebaseai/GenerativeAITextExample/ViewModels/GenerateContentFromTemplateViewModel.swift @@ -41,7 +41,7 @@ import OSLog @MainActor class GenerateContentFromTemplateViewModel: ObservableObject { - private var logger = Logger(subsystem: Bundle.main.bundleIdentifier!, category: "generative-ai") + private var logger = Logger(subsystem: Bundle.main.bundleIdentifier ?? "com.google.firebase.VertexAISample", category: "generative-ai") @Published var outputText = "" From 80809c30a72614be0ac22ee2523a13ca1f3cfd33 Mon Sep 17 00:00:00 2001 From: Paul Beusterien Date: Thu, 13 Nov 2025 16:23:11 -0800 Subject: [PATCH 13/16] fix style --- .../ViewModels/GenerateContentFromTemplateViewModel.swift | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/firebaseai/GenerativeAITextExample/ViewModels/GenerateContentFromTemplateViewModel.swift b/firebaseai/GenerativeAITextExample/ViewModels/GenerateContentFromTemplateViewModel.swift index d9d727b65..86717ef1b 100644 --- a/firebaseai/GenerativeAITextExample/ViewModels/GenerateContentFromTemplateViewModel.swift +++ b/firebaseai/GenerativeAITextExample/ViewModels/GenerateContentFromTemplateViewModel.swift @@ -41,7 +41,10 @@ import OSLog @MainActor class GenerateContentFromTemplateViewModel: ObservableObject { - private var logger = Logger(subsystem: Bundle.main.bundleIdentifier ?? "com.google.firebase.VertexAISample", category: "generative-ai") + private var logger = Logger( + subsystem: Bundle.main.bundleIdentifier ?? "com.google.firebase.VertexAISample", + category: "generative-ai" + ) @Published var outputText = "" From 8b50c345420395ad2ea7de1005e7ad68d639e55a Mon Sep 17 00:00:00 2001 From: Paul Beusterien Date: Fri, 14 Nov 2025 07:31:36 -0800 Subject: [PATCH 14/16] Apply suggestions from code review Co-authored-by: Andrew Heard --- firebaseai/FirebaseAIExample.xcodeproj/project.pbxproj | 4 ++-- .../Screens/GenerateContentFromTemplateScreen.swift | 2 +- .../ViewModels/GenerateContentFromTemplateViewModel.swift | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/firebaseai/FirebaseAIExample.xcodeproj/project.pbxproj b/firebaseai/FirebaseAIExample.xcodeproj/project.pbxproj index 8ba645085..03ed9ea49 100644 --- a/firebaseai/FirebaseAIExample.xcodeproj/project.pbxproj +++ b/firebaseai/FirebaseAIExample.xcodeproj/project.pbxproj @@ -744,7 +744,7 @@ "@executable_path/Frameworks", ); MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = com.google.firebase.VertexAISample; + PRODUCT_BUNDLE_IDENTIFIER = com.google.firebase.quickstart.FirebaseAIExample PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_VERSION = 5.0; @@ -774,7 +774,7 @@ "@executable_path/Frameworks", ); MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = com.google.firebase.VertexAISample; + PRODUCT_BUNDLE_IDENTIFIER = com.google.firebase.quickstart.FirebaseAIExample; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_VERSION = 5.0; diff --git a/firebaseai/GenerativeAITextExample/Screens/GenerateContentFromTemplateScreen.swift b/firebaseai/GenerativeAITextExample/Screens/GenerateContentFromTemplateScreen.swift index 80023d5ad..1b4b28bbe 100644 --- a/firebaseai/GenerativeAITextExample/Screens/GenerateContentFromTemplateScreen.swift +++ b/firebaseai/GenerativeAITextExample/Screens/GenerateContentFromTemplateScreen.swift @@ -1,4 +1,4 @@ -// Copyright 2023 Google LLC +// Copyright 2025 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/firebaseai/GenerativeAITextExample/ViewModels/GenerateContentFromTemplateViewModel.swift b/firebaseai/GenerativeAITextExample/ViewModels/GenerateContentFromTemplateViewModel.swift index 86717ef1b..30f7e1776 100644 --- a/firebaseai/GenerativeAITextExample/ViewModels/GenerateContentFromTemplateViewModel.swift +++ b/firebaseai/GenerativeAITextExample/ViewModels/GenerateContentFromTemplateViewModel.swift @@ -1,4 +1,4 @@ -// Copyright 2023 Google LLC +// Copyright 2025 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. From 3686a68a997095b583e69a12695b648b21d41b37 Mon Sep 17 00:00:00 2001 From: Paul Beusterien Date: Fri, 14 Nov 2025 07:33:56 -0800 Subject: [PATCH 15/16] review suggestion typo --- firebaseai/FirebaseAIExample.xcodeproj/project.pbxproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/firebaseai/FirebaseAIExample.xcodeproj/project.pbxproj b/firebaseai/FirebaseAIExample.xcodeproj/project.pbxproj index 03ed9ea49..015ff616d 100644 --- a/firebaseai/FirebaseAIExample.xcodeproj/project.pbxproj +++ b/firebaseai/FirebaseAIExample.xcodeproj/project.pbxproj @@ -744,7 +744,7 @@ "@executable_path/Frameworks", ); MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = com.google.firebase.quickstart.FirebaseAIExample + PRODUCT_BUNDLE_IDENTIFIER = com.google.firebase.quickstart.FirebaseAIExample; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_VERSION = 5.0; From 101b8ca52766300492b06f48ba25c13de4ca9843 Mon Sep 17 00:00:00 2001 From: Paul Beusterien Date: Fri, 14 Nov 2025 07:39:20 -0800 Subject: [PATCH 16/16] Update firebaseai/GenerativeAITextExample/ViewModels/GenerateContentFromTemplateViewModel.swift Co-authored-by: Andrew Heard --- .../ViewModels/GenerateContentFromTemplateViewModel.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/firebaseai/GenerativeAITextExample/ViewModels/GenerateContentFromTemplateViewModel.swift b/firebaseai/GenerativeAITextExample/ViewModels/GenerateContentFromTemplateViewModel.swift index 30f7e1776..d07544f2e 100644 --- a/firebaseai/GenerativeAITextExample/ViewModels/GenerateContentFromTemplateViewModel.swift +++ b/firebaseai/GenerativeAITextExample/ViewModels/GenerateContentFromTemplateViewModel.swift @@ -42,7 +42,7 @@ import OSLog @MainActor class GenerateContentFromTemplateViewModel: ObservableObject { private var logger = Logger( - subsystem: Bundle.main.bundleIdentifier ?? "com.google.firebase.VertexAISample", + subsystem: Bundle.main.bundleIdentifier ?? "com.google.firebase.quickstart.FirebaseAIExample", category: "generative-ai" )