Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 18 additions & 10 deletions firebaseai/FirebaseAIExample/ContentView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -130,17 +130,25 @@ struct ContentView: View {
case "LiveScreen":
LiveScreen(backendType: selectedBackend, sample: sample)
case "FoundationModelsHybrid":
if #available(iOS 27.0, *) {
HybridAIScreen(backendType: selectedBackend)
} else {
Text("Foundation Models are only available on iOS 27 or newer.")
}
#if canImport(FoundationModels)
if #available(iOS 27.0, *) {
HybridAIScreen(backendType: selectedBackend)
} else {
Text("Foundation Models are only available on iOS 27 or newer.")
}
#else
Text("Foundation Models are not supported on this platform/SDK.")
#endif
case "FoundationModelsVisionID":
if #available(iOS 27.0, *) {
VisionIDScreen(backendType: selectedBackend)
} else {
Text("Foundation Models are only available on iOS 27 or newer.")
}
#if canImport(FoundationModels)
if #available(iOS 27.0, *) {
VisionIDScreen(backendType: selectedBackend)
} else {
Text("Foundation Models are only available on iOS 27 or newer.")
}
#else
Text("Foundation Models are not supported on this platform/SDK.")
#endif
default:
EmptyView()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,27 +12,29 @@
// See the License for the specific language governing permissions and
// limitations under the License.

import Foundation
import FoundationModels
#if canImport(FoundationModels)
import Foundation
import FoundationModels

@available(iOS 26.0, *)
@Generable
struct IdentifiedObject: Equatable, Codable {
@Guide(description: "The name of the primary object or landmark detected.")
let name: String
@available(iOS 26.0, *)
@Generable
struct IdentifiedObject: Equatable, Codable {
@Guide(description: "The name of the primary object or landmark detected.")
let name: String

@Guide(
description: "The category of the object (e.g. Landmark, Plant, Food, Animal, Device, Clothing)."
)
let category: String
@Guide(
description: "The category of the object (e.g. Landmark, Plant, Food, Animal, Device, Clothing)."
)
let category: String

@Guide(description: "A short, 2-sentence description of the object and interesting facts.")
let description: String
}
@Guide(description: "A short, 2-sentence description of the object and interesting facts.")
let description: String
}

@available(iOS 26.0, *)
@Generable
struct TextSummary: Equatable, Codable {
@Guide(description: "A list of exactly 2 key summary points.")
let summaryPoints: [String]
}
@available(iOS 26.0, *)
@Generable
struct TextSummary: Equatable, Codable {
@Guide(description: "A list of exactly 2 key summary points.")
let summaryPoints: [String]
}
#endif
Original file line number Diff line number Diff line change
Expand Up @@ -12,19 +12,21 @@
// See the License for the specific language governing permissions and
// limitations under the License.

import SwiftUI
#if canImport(FoundationModels)
import SwiftUI

@available(iOS 27.0, *)
struct HybridAIScreen: View {
@StateObject private var viewModel: HybridAIViewModel
@available(iOS 27.0, *)
struct HybridAIScreen: View {
@StateObject private var viewModel: HybridAIViewModel

init(backendType: BackendOption) {
_viewModel = StateObject(wrappedValue: HybridAIViewModel(backendType: backendType))
}
init(backendType: BackendOption) {
_viewModel = StateObject(wrappedValue: HybridAIViewModel(backendType: backendType))
}

var body: some View {
FoundationModelsContainer(viewModel: viewModel, title: "Hybrid AI") { vm in
HybridAIView(viewModel: vm)
var body: some View {
FoundationModelsContainer(viewModel: viewModel, title: "Hybrid AI") { vm in
HybridAIView(viewModel: vm)
}
}
}
}
#endif
Original file line number Diff line number Diff line change
Expand Up @@ -12,21 +12,23 @@
// See the License for the specific language governing permissions and
// limitations under the License.

import SwiftUI
import PhotosUI
#if canImport(FoundationModels)
import SwiftUI
import PhotosUI

@available(iOS 27.0, *)
struct VisionIDScreen: View {
@StateObject private var viewModel: VisionIDViewModel
@State private var photosPickerItem: PhotosPickerItem? = nil
@available(iOS 27.0, *)
struct VisionIDScreen: View {
@StateObject private var viewModel: VisionIDViewModel
@State private var photosPickerItem: PhotosPickerItem? = nil

init(backendType: BackendOption) {
_viewModel = StateObject(wrappedValue: VisionIDViewModel(backendType: backendType))
}
init(backendType: BackendOption) {
_viewModel = StateObject(wrappedValue: VisionIDViewModel(backendType: backendType))
}

var body: some View {
FoundationModelsContainer(viewModel: viewModel, title: "Vision ID") { vm in
VisionIDView(viewModel: vm, photosPickerItem: $photosPickerItem)
var body: some View {
FoundationModelsContainer(viewModel: viewModel, title: "Vision ID") { vm in
VisionIDView(viewModel: vm, photosPickerItem: $photosPickerItem)
}
}
}
}
#endif
Original file line number Diff line number Diff line change
Expand Up @@ -12,47 +12,49 @@
// See the License for the specific language governing permissions and
// limitations under the License.

import Foundation
import SwiftUI
import Combine
import FoundationModels
import FirebaseAILogic

enum ModelPreference: String, CaseIterable, Identifiable {
case auto = "Auto (Local First)"
case cloud = "Cloud Only"

var id: String { rawValue }
}

@available(iOS 27.0, *)
@MainActor
class FoundationModelsBaseViewModel: ObservableObject {
@Published var inProgress = false
@Published var error: Error?
@Published var modelPreference: ModelPreference = .auto
@Published var isUsingLocalModel: Bool = false

internal var activeTask: Task<Void, Never>?
let backendType: BackendOption

init(backendType: BackendOption) {
self.backendType = backendType
#if canImport(FoundationModels)
import Foundation
import SwiftUI
import Combine
import FoundationModels
import FirebaseAILogic

enum ModelPreference: String, CaseIterable, Identifiable {
case auto = "Auto (Local First)"
case cloud = "Cloud Only"

var id: String { rawValue }
}

func stopActiveTask() {
activeTask?.cancel()
activeTask = nil
inProgress = false
}
@available(iOS 27.0, *)
@MainActor
class FoundationModelsBaseViewModel: ObservableObject {
@Published var inProgress = false
@Published var error: Error?
@Published var modelPreference: ModelPreference = .auto
@Published var isUsingLocalModel: Bool = false

internal var activeTask: Task<Void, Never>?
let backendType: BackendOption

init(backendType: BackendOption) {
self.backendType = backendType
}

func stopActiveTask() {
activeTask?.cancel()
activeTask = nil
inProgress = false
}

internal func getFirebaseAI() -> FirebaseAI {
switch backendType {
case .googleAI:
return FirebaseAI.firebaseAI(backend: .googleAI())
case .vertexAI:
// Using "global" as location for Foundation Models fallback compatibility
return FirebaseAI.firebaseAI(backend: .vertexAI(location: "global"))
internal func getFirebaseAI() -> FirebaseAI {
switch backendType {
case .googleAI:
return FirebaseAI.firebaseAI(backend: .googleAI())
case .vertexAI:
// Using "global" as location for Foundation Models fallback compatibility
return FirebaseAI.firebaseAI(backend: .vertexAI(location: "global"))
}
}
}
}
#endif
Original file line number Diff line number Diff line change
Expand Up @@ -12,40 +12,70 @@
// See the License for the specific language governing permissions and
// limitations under the License.

import Foundation
import SwiftUI
import Combine
import FoundationModels
import FirebaseAILogic
#if canImport(FoundationModels)
import Foundation
import SwiftUI
import Combine
import FoundationModels
import FirebaseAILogic

@available(iOS 27.0, *)
@MainActor
final class HybridAIViewModel: FoundationModelsBaseViewModel {
@Published var inputText: String =
"It is the quintessential autumn harvest fruit, famously baked into warm cinnamon pastries, dipped in sticky caramel on Halloween, and traditionally rumored to keep medical professionals at bay if eaten once a day."
@Published var outputSummary: TextSummary?
@available(iOS 27.0, *)
@MainActor
final class HybridAIViewModel: FoundationModelsBaseViewModel {
@Published var inputText: String =
"It is the quintessential autumn harvest fruit, famously baked into warm cinnamon pastries, dipped in sticky caramel on Halloween, and traditionally rumored to keep medical professionals at bay if eaten once a day."
@Published var outputSummary: TextSummary?

func runSummarization() {
stopActiveTask()
inProgress = true
error = nil
outputSummary = nil
func runSummarization() {
stopActiveTask()
inProgress = true
error = nil
outputSummary = nil

activeTask = Task {
defer { self.inProgress = false }
activeTask = Task {
defer { self.inProgress = false }

let instructions = Instructions {
"Your job is to summarize the provided text in exactly 2 bullet points."
}
let instructions = Instructions {
"Your job is to summarize the provided text in exactly 2 bullet points."
}

let availability = SystemLanguageModel.default.availability
let availability = SystemLanguageModel.default.availability

// Try local model first if it reports available and not forced to cloud
if modelPreference == .auto, availability == .available {
isUsingLocalModel = true
do {
let session = LanguageModelSession(
model: SystemLanguageModel.default,
instructions: instructions
)
let response = try await session.respond(
to: inputText,
generating: TextSummary.self
)
if !Task.isCancelled {
self.outputSummary = response.content
}
return
} catch {
// Fall back to cloud model if local model fails
if error.isMLAssetUnavailable {
print("Local ML assets unavailable. Falling back to cloud model...")
} else {
print(
"Local model failed: \(error.localizedDescription). Falling back to cloud model..."
)
}
}
}

// Try local model first if it reports available and not forced to cloud
if modelPreference == .auto, availability == .available {
isUsingLocalModel = true
// Fallback to cloud model
isUsingLocalModel = false
do {
let ai = getFirebaseAI()
let model = ai.geminiLanguageModel(name: "gemini-3.1-flash-lite")
let session = LanguageModelSession(
model: SystemLanguageModel.default,
model: model,
instructions: instructions
)
let response = try await session.respond(
Expand All @@ -55,40 +85,12 @@ final class HybridAIViewModel: FoundationModelsBaseViewModel {
if !Task.isCancelled {
self.outputSummary = response.content
}
return
} catch {
// Fall back to cloud model if local model fails
if error.isMLAssetUnavailable {
print("Local ML assets unavailable. Falling back to cloud model...")
} else {
print(
"Local model failed: \(error.localizedDescription). Falling back to cloud model..."
)
if !Task.isCancelled {
self.error = error
}
}
}

// Fallback to cloud model
isUsingLocalModel = false
do {
let ai = getFirebaseAI()
let model = ai.geminiLanguageModel(name: "gemini-3.1-flash-lite")
let session = LanguageModelSession(
model: model,
instructions: instructions
)
let response = try await session.respond(
to: inputText,
generating: TextSummary.self
)
if !Task.isCancelled {
self.outputSummary = response.content
}
} catch {
if !Task.isCancelled {
self.error = error
}
}
}
}
}
#endif
Loading
Loading