diff --git a/firebaseai/ChatSample/Screens/ConversationScreen.swift b/firebaseai/ChatSample/Screens/ConversationScreen.swift index 8d7c3c1a8..0b845efd1 100644 --- a/firebaseai/ChatSample/Screens/ConversationScreen.swift +++ b/firebaseai/ChatSample/Screens/ConversationScreen.swift @@ -17,12 +17,18 @@ import GenerativeAIUIComponents import SwiftUI struct ConversationScreen: View { - @EnvironmentObject - var viewModel: ConversationViewModel + let firebaseService: FirebaseAI + @StateObject var viewModel: ConversationViewModel @State private var userPrompt = "" + init(firebaseService: FirebaseAI) { + self.firebaseService = firebaseService + _viewModel = + StateObject(wrappedValue: ConversationViewModel(firebaseService: firebaseService)) + } + enum FocusedField: Hashable { case message } @@ -110,11 +116,11 @@ struct ConversationScreen: View { struct ConversationScreen_Previews: PreviewProvider { struct ContainerView: View { - @StateObject var viewModel = ConversationViewModel() + @StateObject var viewModel = ConversationViewModel(firebaseService: FirebaseAI + .firebaseAI()) // Example service init var body: some View { - ConversationScreen() - .environmentObject(viewModel) + ConversationScreen(firebaseService: FirebaseAI.firebaseAI()) .onAppear { viewModel.messages = ChatMessage.samples } @@ -123,7 +129,7 @@ struct ConversationScreen_Previews: PreviewProvider { static var previews: some View { NavigationStack { - ConversationScreen() + ConversationScreen(firebaseService: FirebaseAI.firebaseAI()) } } } diff --git a/firebaseai/ChatSample/ViewModels/ConversationViewModel.swift b/firebaseai/ChatSample/ViewModels/ConversationViewModel.swift index 1f77b8ff9..29cc06db4 100644 --- a/firebaseai/ChatSample/ViewModels/ConversationViewModel.swift +++ b/firebaseai/ChatSample/ViewModels/ConversationViewModel.swift @@ -35,10 +35,8 @@ class ConversationViewModel: ObservableObject { private var chatTask: Task? - init() { - // model = FirebaseAI.firebaseAI(backend: .vertexAI()).generativeModel(modelName: "gemini-2.0-flash-001") - model = FirebaseAI.firebaseAI(backend: .googleAI()) - .generativeModel(modelName: "gemini-2.0-flash-001") + init(firebaseService: FirebaseAI) { + model = firebaseService.generativeModel(modelName: "gemini-2.0-flash-001") chat = model.startChat() } diff --git a/firebaseai/FirebaseAISample/ContentView.swift b/firebaseai/FirebaseAISample/ContentView.swift index 2c13af833..af7f2035f 100644 --- a/firebaseai/FirebaseAISample/ContentView.swift +++ b/firebaseai/FirebaseAISample/ContentView.swift @@ -13,45 +13,72 @@ // limitations under the License. import SwiftUI +import FirebaseAI -struct ContentView: View { - @StateObject - var viewModel = ConversationViewModel() +enum BackendOption: String, CaseIterable, Identifiable { + case googleAI = "Google AI" + case vertexAI = "Vertex AI" + var id: String { rawValue } + + var backendValue: FirebaseAI { + switch self { + case .googleAI: + return FirebaseAI.firebaseAI(backend: .googleAI()) + case .vertexAI: + return FirebaseAI.firebaseAI(backend: .vertexAI()) + } + } +} - @StateObject - var functionCallingViewModel = FunctionCallingViewModel() +struct ContentView: View { + @State private var selectedBackend: BackendOption = .googleAI + @State private var firebaseService: FirebaseAI = FirebaseAI.firebaseAI(backend: .googleAI()) var body: some View { NavigationStack { List { - NavigationLink { - SummarizeScreen() - } label: { - Label("Text", systemImage: "doc.text") + Section("Configuration") { + Picker("Backend", selection: $selectedBackend) { + ForEach(BackendOption.allCases) { option in + Text(option.rawValue).tag(option) + } + } } - NavigationLink { - PhotoReasoningScreen() - } label: { - Label("Multi-modal", systemImage: "doc.richtext") - } - NavigationLink { - ConversationScreen() - .environmentObject(viewModel) - } label: { - Label("Chat", systemImage: "ellipsis.message.fill") - } - NavigationLink { - FunctionCallingScreen().environmentObject(functionCallingViewModel) - } label: { - Label("Function Calling", systemImage: "function") - } - NavigationLink { - ImagenScreen() - } label: { - Label("Imagen", systemImage: "camera.circle") + + Section("Samples") { + NavigationLink { + SummarizeScreen(firebaseService: firebaseService) + } label: { + Label("Text", systemImage: "doc.text") + } + NavigationLink { + PhotoReasoningScreen(firebaseService: firebaseService) + } label: { + Label("Multi-modal", systemImage: "doc.richtext") + } + NavigationLink { + ConversationScreen(firebaseService: firebaseService) + } label: { + Label("Chat", systemImage: "ellipsis.message.fill") + } + NavigationLink { + FunctionCallingScreen(firebaseService: firebaseService) + } label: { + Label("Function Calling", systemImage: "function") + } + NavigationLink { + ImagenScreen(firebaseService: firebaseService) + } label: { + Label("Imagen", systemImage: "camera.circle") + } } } .navigationTitle("Generative AI Samples") + .onChange(of: selectedBackend) { newBackend in + firebaseService = newBackend.backendValue + // Note: This might cause views that hold the old service instance to misbehave + // unless they are also correctly updated or recreated. + } } } } diff --git a/firebaseai/FunctionCallingSample/Screens/FunctionCallingScreen.swift b/firebaseai/FunctionCallingSample/Screens/FunctionCallingScreen.swift index 71de0c2cf..474a55ede 100644 --- a/firebaseai/FunctionCallingSample/Screens/FunctionCallingScreen.swift +++ b/firebaseai/FunctionCallingSample/Screens/FunctionCallingScreen.swift @@ -17,12 +17,18 @@ import GenerativeAIUIComponents import SwiftUI struct FunctionCallingScreen: View { - @EnvironmentObject - var viewModel: FunctionCallingViewModel + let firebaseService: FirebaseAI + @StateObject var viewModel: FunctionCallingViewModel @State private var userPrompt = "What is 100 Euros in U.S. Dollars?" + init(firebaseService: FirebaseAI) { + self.firebaseService = firebaseService + _viewModel = + StateObject(wrappedValue: FunctionCallingViewModel(firebaseService: firebaseService)) + } + enum FocusedField: Hashable { case message } @@ -112,11 +118,10 @@ struct FunctionCallingScreen: View { struct FunctionCallingScreen_Previews: PreviewProvider { struct ContainerView: View { - @EnvironmentObject - var viewModel: FunctionCallingViewModel + @StateObject var viewModel = FunctionCallingViewModel(firebaseService: FirebaseAI.firebaseAI()) var body: some View { - FunctionCallingScreen() + FunctionCallingScreen(firebaseService: FirebaseAI.firebaseAI()) .onAppear { viewModel.messages = ChatMessage.samples } @@ -125,7 +130,7 @@ struct FunctionCallingScreen_Previews: PreviewProvider { static var previews: some View { NavigationStack { - FunctionCallingScreen().environmentObject(FunctionCallingViewModel()) + FunctionCallingScreen(firebaseService: FirebaseAI.firebaseAI()) } } } diff --git a/firebaseai/FunctionCallingSample/ViewModels/FunctionCallingViewModel.swift b/firebaseai/FunctionCallingSample/ViewModels/FunctionCallingViewModel.swift index 2c04c6a6d..8bd7f90ef 100644 --- a/firebaseai/FunctionCallingSample/ViewModels/FunctionCallingViewModel.swift +++ b/firebaseai/FunctionCallingSample/ViewModels/FunctionCallingViewModel.swift @@ -37,12 +37,11 @@ class FunctionCallingViewModel: ObservableObject { private var chatTask: Task? - init() { - // model = FirebaseAI.firebaseAI(backend: .vertexAI()).generativeModel( - model = FirebaseAI.firebaseAI(backend: .googleAI()).generativeModel( - modelName: "gemini-2.0-flash-001", - tools: [.functionDeclarations([ - FunctionDeclaration( + init(firebaseService: FirebaseAI) { // Accept FirebaseAI instance + model = firebaseService.generativeModel( + modelName: "gemini-2.0-flash-001", + tools: [.functionDeclarations([ + FunctionDeclaration( name: "get_exchange_rate", description: "Get the exchange rate for currencies between countries", parameters: [ @@ -57,8 +56,8 @@ class FunctionCallingViewModel: ObservableObject { ] ), ])] - ) - chat = model.startChat() + ) + chat = model.startChat() // Initialize chat with the model from the service } func sendMessage(_ text: String, streaming: Bool = true) async { diff --git a/firebaseai/GenerativeAIMultimodalSample/Screens/PhotoReasoningScreen.swift b/firebaseai/GenerativeAIMultimodalSample/Screens/PhotoReasoningScreen.swift index 930214770..e3796f387 100644 --- a/firebaseai/GenerativeAIMultimodalSample/Screens/PhotoReasoningScreen.swift +++ b/firebaseai/GenerativeAIMultimodalSample/Screens/PhotoReasoningScreen.swift @@ -16,9 +16,17 @@ import GenerativeAIUIComponents import MarkdownUI import PhotosUI import SwiftUI +import FirebaseAI struct PhotoReasoningScreen: View { - @StateObject var viewModel = PhotoReasoningViewModel() + let firebaseService: FirebaseAI + @StateObject var viewModel: PhotoReasoningViewModel + + init(firebaseService: FirebaseAI) { + self.firebaseService = firebaseService + _viewModel = + StateObject(wrappedValue: PhotoReasoningViewModel(firebaseService: firebaseService)) + } enum FocusedField: Hashable { case message @@ -73,6 +81,6 @@ struct PhotoReasoningScreen: View { #Preview { NavigationStack { - PhotoReasoningScreen() + PhotoReasoningScreen(firebaseService: FirebaseAI.firebaseAI()) } } diff --git a/firebaseai/GenerativeAIMultimodalSample/ViewModels/PhotoReasoningViewModel.swift b/firebaseai/GenerativeAIMultimodalSample/ViewModels/PhotoReasoningViewModel.swift index b64d167b9..24a2e96e2 100644 --- a/firebaseai/GenerativeAIMultimodalSample/ViewModels/PhotoReasoningViewModel.swift +++ b/firebaseai/GenerativeAIMultimodalSample/ViewModels/PhotoReasoningViewModel.swift @@ -43,10 +43,8 @@ class PhotoReasoningViewModel: ObservableObject { private var model: GenerativeModel? - init() { - // model = FirebaseAI.firebaseAI(backend: .vertexAI()).generativeModel(modelName: "gemini-2.0-flash-001") - model = FirebaseAI.firebaseAI(backend: .googleAI()) - .generativeModel(modelName: "gemini-2.0-flash-001") + init(firebaseService: FirebaseAI) { + model = firebaseService.generativeModel(modelName: "gemini-2.0-flash-001") } func reason() async { diff --git a/firebaseai/GenerativeAITextSample/Screens/SummarizeScreen.swift b/firebaseai/GenerativeAITextSample/Screens/SummarizeScreen.swift index 748c1addd..f11bcafc5 100644 --- a/firebaseai/GenerativeAITextSample/Screens/SummarizeScreen.swift +++ b/firebaseai/GenerativeAITextSample/Screens/SummarizeScreen.swift @@ -14,11 +14,18 @@ import MarkdownUI import SwiftUI +import FirebaseAI struct SummarizeScreen: View { - @StateObject var viewModel = SummarizeViewModel() + let firebaseService: FirebaseAI + @StateObject var viewModel: SummarizeViewModel @State var userInput = "" + init(firebaseService: FirebaseAI) { + self.firebaseService = firebaseService + _viewModel = StateObject(wrappedValue: SummarizeViewModel(firebaseService: firebaseService)) + } + enum FocusedField: Hashable { case message } @@ -75,6 +82,6 @@ struct SummarizeScreen: View { #Preview { NavigationStack { - SummarizeScreen() + SummarizeScreen(firebaseService: FirebaseAI.firebaseAI()) } } diff --git a/firebaseai/GenerativeAITextSample/ViewModels/SummarizeViewModel.swift b/firebaseai/GenerativeAITextSample/ViewModels/SummarizeViewModel.swift index 512ee1c80..94c41e70a 100644 --- a/firebaseai/GenerativeAITextSample/ViewModels/SummarizeViewModel.swift +++ b/firebaseai/GenerativeAITextSample/ViewModels/SummarizeViewModel.swift @@ -31,10 +31,8 @@ class SummarizeViewModel: ObservableObject { private var model: GenerativeModel? - init() { - // model = FirebaseAI.firebaseAI(backend: .vertexAI()).generativeModel(modelName: "gemini-2.0-flash-001") - model = FirebaseAI.firebaseAI(backend: .googleAI()) - .generativeModel(modelName: "gemini-2.0-flash-001") + init(firebaseService: FirebaseAI) { + model = firebaseService.generativeModel(modelName: "gemini-2.0-flash-001") } func summarize(inputText: String) async { diff --git a/firebaseai/ImagenScreen/ImagenScreen.swift b/firebaseai/ImagenScreen/ImagenScreen.swift index 3d621bb41..e961fef56 100644 --- a/firebaseai/ImagenScreen/ImagenScreen.swift +++ b/firebaseai/ImagenScreen/ImagenScreen.swift @@ -14,9 +14,16 @@ import SwiftUI import GenerativeAIUIComponents +import FirebaseAI struct ImagenScreen: View { - @StateObject var viewModel = ImagenViewModel() + let firebaseService: FirebaseAI + @StateObject var viewModel: ImagenViewModel + + init(firebaseService: FirebaseAI) { + self.firebaseService = firebaseService + _viewModel = StateObject(wrappedValue: ImagenViewModel(firebaseService: firebaseService)) + } enum FocusedField: Hashable { case message @@ -103,5 +110,5 @@ struct ProgressOverlay: View { } #Preview { - ImagenScreen() + ImagenScreen(firebaseService: FirebaseAI.firebaseAI()) } diff --git a/firebaseai/ImagenScreen/ImagenViewModel.swift b/firebaseai/ImagenScreen/ImagenViewModel.swift index 3997ce156..d4fc2b43f 100644 --- a/firebaseai/ImagenScreen/ImagenViewModel.swift +++ b/firebaseai/ImagenScreen/ImagenViewModel.swift @@ -37,11 +37,7 @@ class ImagenViewModel: ObservableObject { private var generateImagesTask: Task? - // 1. Initialize the Gemini service - // private let service = FirebaseAI.firebaseAI(backend: .vertexAI()) - private let service = FirebaseAI.firebaseAI(backend: .googleAI()) - init() { - // 2. Configure Imagen settings + init(firebaseService: FirebaseAI) { let modelName = "imagen-3.0-generate-002" let safetySettings = ImagenSafetySettings( safetyFilterLevel: .blockLowAndAbove @@ -50,8 +46,7 @@ class ImagenViewModel: ObservableObject { generationConfig.numberOfImages = 4 generationConfig.aspectRatio = .landscape4x3 - // 3. Initialize the Imagen model - model = service.imagenModel( + model = firebaseService.imagenModel( modelName: modelName, generationConfig: generationConfig, safetySettings: safetySettings