Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
27 changes: 27 additions & 0 deletions firebaseai/ChatExample/Models/ChatMessage.swift
Original file line number Diff line number Diff line change
Expand Up @@ -70,3 +70,30 @@ extension ChatMessage {

static var sample = samples[0]
}

extension ChatMessage {
// Convert ModelContent to ChatMessage
static func from(_ modelContent: ModelContent) -> ChatMessage? {
// Extract text from parts - parts is Array<Part>
guard let textPart = modelContent.parts.first as? TextPart else {
return nil
}

let participant: Participant
switch modelContent.role {
case "user":
participant = .user
case "model":
participant = .system
default:
return nil
}

return ChatMessage(message: textPart.text, participant: participant)
}

// Convert array of ModelContent to array of ChatMessage
static func from(_ modelContents: [ModelContent]) -> [ChatMessage] {
return modelContents.compactMap { from($0) }
}
}
26 changes: 15 additions & 11 deletions firebaseai/ChatExample/Screens/ConversationScreen.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,22 +18,16 @@ import SwiftUI

struct ConversationScreen: View {
let firebaseService: FirebaseAI
let title: String
@StateObject var viewModel: ConversationViewModel

@State
private var userPrompt = ""

init(firebaseService: FirebaseAI, title: String, searchGroundingEnabled: Bool = false) {
let model = firebaseService.generativeModel(
modelName: "gemini-2.0-flash-001",
tools: searchGroundingEnabled ? [.googleSearch()] : []
)
self.title = title
init(firebaseService: FirebaseAI, sampleId: UUID? = nil) {
self.firebaseService = firebaseService
_viewModel =
StateObject(wrappedValue: ConversationViewModel(firebaseService: firebaseService,
model: model))
sampleId: sampleId))
}

enum FocusedField: Hashable {
Expand Down Expand Up @@ -89,15 +83,24 @@ struct ConversationScreen: View {
focusedField = nil
}
.toolbar {
ToolbarItem(placement: .principal) {
Text(viewModel.title)
.font(.system(size: 24, weight: .bold))
.foregroundColor(.primary)
.padding(.top, 10)
}
ToolbarItem(placement: .primaryAction) {
Button(action: newChat) {
Image(systemName: "square.and.pencil")
}
}
}
.navigationTitle(title)
.onAppear {
focusedField = .message
// Set initial prompt from viewModel if available
if userPrompt.isEmpty && !viewModel.initialPrompt.isEmpty {
userPrompt = viewModel.initialPrompt
}
}
}

Expand All @@ -121,6 +124,7 @@ struct ConversationScreen: View {

private func newChat() {
viewModel.startNewChat()
userPrompt = ""
}
}

Expand All @@ -130,7 +134,7 @@ struct ConversationScreen_Previews: PreviewProvider {
.firebaseAI()) // Example service init

var body: some View {
ConversationScreen(firebaseService: FirebaseAI.firebaseAI(), title: "Chat sample")
ConversationScreen(firebaseService: FirebaseAI.firebaseAI())
.onAppear {
viewModel.messages = ChatMessage.samples
}
Expand All @@ -139,7 +143,7 @@ struct ConversationScreen_Previews: PreviewProvider {

static var previews: some View {
NavigationStack {
ConversationScreen(firebaseService: FirebaseAI.firebaseAI(), title: "Chat sample")
ConversationScreen(firebaseService: FirebaseAI.firebaseAI())
}
}
}
33 changes: 26 additions & 7 deletions firebaseai/ChatExample/ViewModels/ConversationViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import FirebaseAI
import Foundation
import UIKit
import GenerativeAIUIComponents

@MainActor
class ConversationViewModel: ObservableObject {
Expand All @@ -29,21 +30,38 @@ class ConversationViewModel: ObservableObject {
return error != nil
}

@Published var initialPrompt: String = ""
@Published var title: String = ""

private var model: GenerativeModel
private var chat: Chat
private var stopGenerating = false

private var chatTask: Task<Void, Never>?

init(firebaseService: FirebaseAI, model: GenerativeModel? = nil) {
if let model {
self.model = model
private var sample: Sample?

init(firebaseService: FirebaseAI, sampleId: UUID? = nil) {
// retrieve sample from sampleId
sample = Sample.find(by: sampleId)

// create a generative model with sample data
model = firebaseService.generativeModel(
modelName: "gemini-2.0-flash-001",
tools: sample?.tools,
systemInstruction: sample?.systemInstruction
)

if let chatHistory = sample?.chatHistory, !chatHistory.isEmpty {
// Initialize with sample chat history if it's available
messages = ChatMessage.from(chatHistory)
chat = model.startChat(history: chatHistory)
} else {
self.model = firebaseService.generativeModel(
modelName: "gemini-2.0-flash-001"
)
chat = model.startChat()
}
chat = self.model.startChat()

initialPrompt = sample?.initialPrompt ?? ""
title = sample?.title ?? ""
}

func sendMessage(_ text: String, streaming: Bool = true) async {
Expand All @@ -60,6 +78,7 @@ class ConversationViewModel: ObservableObject {
error = nil
chat = model.startChat()
messages.removeAll()
initialPrompt = ""
}

func stop() {
Expand Down
62 changes: 44 additions & 18 deletions firebaseai/FirebaseAIExample.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@
archiveVersion = 1;
classes = {
};
objectVersion = 56;
objectVersion = 60;
objects = {

/* Begin PBXBuildFile section */
7200F3082E3A054300CDC51C /* GenerativeAIUIComponents in Frameworks */ = {isa = PBXBuildFile; productRef = 7200F3072E3A054300CDC51C /* GenerativeAIUIComponents */; };
72DA044F2E385DF3004FED7D /* ChatMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 72DA044E2E385DF3004FED7D /* ChatMessage.swift */; };
869200B32B879C4F00482873 /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 869200B22B879C4F00482873 /* GoogleService-Info.plist */; };
86C1F4832BC726150026816F /* FunctionCallingScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 86C1F47E2BC726150026816F /* FunctionCallingScreen.swift */; };
86C1F4842BC726150026816F /* FunctionCallingViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 86C1F4802BC726150026816F /* FunctionCallingViewModel.swift */; };
Expand All @@ -22,11 +24,11 @@
886F95DB2B17BAEF0036F07A /* PhotoReasoningViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8802666F2B0FC39000CF7CB6 /* PhotoReasoningViewModel.swift */; };
886F95DC2B17BAEF0036F07A /* PhotoReasoningScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 880266752B0FC39000CF7CB6 /* PhotoReasoningScreen.swift */; };
886F95DD2B17D5010036F07A /* MessageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88E10F5A2B11133E00C08E95 /* MessageView.swift */; };
886F95DE2B17D5010036F07A /* ChatMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88E10F582B11131900C08E95 /* ChatMessage.swift */; };
886F95DF2B17D5010036F07A /* BouncingDots.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88E10F5C2B11135000C08E95 /* BouncingDots.swift */; };
886F95E02B17D5010036F07A /* ConversationViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88E10F562B1112F600C08E95 /* ConversationViewModel.swift */; };
886F95E12B17D5010036F07A /* ConversationScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88E10F542B1112CA00C08E95 /* ConversationScreen.swift */; };
886F95E32B17D6630036F07A /* GenerativeAIUIComponents in Frameworks */ = {isa = PBXBuildFile; productRef = 886F95E22B17D6630036F07A /* GenerativeAIUIComponents */; };
A5E8E3C92C3B4F388A7A4A19 /* FilterChipView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5E8E3C52C3B4F388A7A4A15 /* FilterChipView.swift */; };
A5E8E3CA2C3B4F388A7A4A1A /* SampleCardView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5E8E3C62C3B4F388A7A4A16 /* SampleCardView.swift */; };
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 */; };
Expand All @@ -35,6 +37,8 @@
/* End PBXBuildFile section */

/* Begin PBXFileReference section */
726634072E37011C00554974 /* Package.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = Package.swift; path = GenerativeAIUIComponents/Package.swift; sourceTree = "<group>"; };
72DA044E2E385DF3004FED7D /* ChatMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatMessage.swift; sourceTree = "<group>"; };
869200B22B879C4F00482873 /* GoogleService-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "GoogleService-Info.plist"; sourceTree = "<group>"; };
86C1F47E2BC726150026816F /* FunctionCallingScreen.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FunctionCallingScreen.swift; sourceTree = "<group>"; };
86C1F4802BC726150026816F /* FunctionCallingViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FunctionCallingViewModel.swift; sourceTree = "<group>"; };
Expand All @@ -58,9 +62,10 @@
88E10F4B2B110D5400C08E95 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = "<group>"; };
88E10F542B1112CA00C08E95 /* ConversationScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConversationScreen.swift; sourceTree = "<group>"; };
88E10F562B1112F600C08E95 /* ConversationViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConversationViewModel.swift; sourceTree = "<group>"; };
88E10F582B11131900C08E95 /* ChatMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatMessage.swift; sourceTree = "<group>"; };
88E10F5A2B11133E00C08E95 /* MessageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageView.swift; sourceTree = "<group>"; };
88E10F5C2B11135000C08E95 /* BouncingDots.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BouncingDots.swift; sourceTree = "<group>"; };
A5E8E3C52C3B4F388A7A4A15 /* FilterChipView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FilterChipView.swift; sourceTree = "<group>"; };
A5E8E3C62C3B4F388A7A4A16 /* SampleCardView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SampleCardView.swift; sourceTree = "<group>"; };
AEE793DC2E256D3900708F02 /* GoogleSearchSuggestionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GoogleSearchSuggestionView.swift; sourceTree = "<group>"; };
AEE793DD2E256D3900708F02 /* GroundedResponseView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroundedResponseView.swift; sourceTree = "<group>"; };
DEFECAA62D7B4CCD00EF9621 /* ImagenScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImagenScreen.swift; sourceTree = "<group>"; };
Expand All @@ -74,13 +79,21 @@
files = (
DE26D95F2DBB3E9F007E6668 /* FirebaseAI in Frameworks */,
886F95D82B17BA420036F07A /* MarkdownUI in Frameworks */,
886F95E32B17D6630036F07A /* GenerativeAIUIComponents in Frameworks */,
7200F3082E3A054300CDC51C /* GenerativeAIUIComponents in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */

/* Begin PBXGroup section */
72DA044D2E385DED004FED7D /* Models */ = {
isa = PBXGroup;
children = (
72DA044E2E385DF3004FED7D /* ChatMessage.swift */,
);
path = Models;
sourceTree = "<group>";
};
86C1F47F2BC726150026816F /* Screens */ = {
isa = PBXGroup;
children = (
Expand Down Expand Up @@ -141,6 +154,7 @@
88209C222B0FBE1700F64795 /* Frameworks */ = {
isa = PBXGroup;
children = (
726634072E37011C00554974 /* Package.swift */,
);
name = Frameworks;
sourceTree = "<group>";
Expand Down Expand Up @@ -176,6 +190,7 @@
8848C8342B0D04BC007B434F /* ContentView.swift */,
8848C8362B0D04BD007B434F /* Assets.xcassets */,
8848C8382B0D04BD007B434F /* Preview Content */,
A5E8E3C22C3B4F388A7A4A12 /* Views */,
);
path = FirebaseAIExample;
sourceTree = "<group>";
Expand Down Expand Up @@ -229,7 +244,7 @@
88E10F432B110D5300C08E95 /* ChatExample */ = {
isa = PBXGroup;
children = (
88E10F522B11124A00C08E95 /* Models */,
72DA044D2E385DED004FED7D /* Models */,
88E10F502B11123600C08E95 /* ViewModels */,
88E10F512B11124100C08E95 /* Views */,
88E10F532B1112B900C08E95 /* Screens */,
Expand Down Expand Up @@ -267,20 +282,21 @@
path = Views;
sourceTree = "<group>";
};
88E10F522B11124A00C08E95 /* Models */ = {
88E10F532B1112B900C08E95 /* Screens */ = {
isa = PBXGroup;
children = (
88E10F582B11131900C08E95 /* ChatMessage.swift */,
88E10F542B1112CA00C08E95 /* ConversationScreen.swift */,
);
path = Models;
path = Screens;
sourceTree = "<group>";
};
88E10F532B1112B900C08E95 /* Screens */ = {
A5E8E3C22C3B4F388A7A4A12 /* Views */ = {
isa = PBXGroup;
children = (
88E10F542B1112CA00C08E95 /* ConversationScreen.swift */,
A5E8E3C52C3B4F388A7A4A15 /* FilterChipView.swift */,
A5E8E3C62C3B4F388A7A4A16 /* SampleCardView.swift */,
);
path = Screens;
path = Views;
sourceTree = "<group>";
};
AEE793DE2E256D3900708F02 /* Grounding */ = {
Expand Down Expand Up @@ -319,8 +335,8 @@
name = FirebaseAIExample;
packageProductDependencies = (
886F95D72B17BA420036F07A /* MarkdownUI */,
886F95E22B17D6630036F07A /* GenerativeAIUIComponents */,
DE26D95E2DBB3E9F007E6668 /* FirebaseAI */,
7200F3072E3A054300CDC51C /* GenerativeAIUIComponents */,
);
productName = GenerativeAIExample;
productReference = 8848C82F2B0D04BC007B434F /* FirebaseAIExample.app */;
Expand Down Expand Up @@ -354,6 +370,7 @@
88209C212B0FBDF700F64795 /* XCRemoteSwiftPackageReference "swift-markdown-ui" */,
DEA09AC32B1FCE22001962D9 /* XCRemoteSwiftPackageReference "NetworkImage" */,
DEFECAAB2D7BB49700EF9621 /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */,
7200F3062E3A054300CDC51C /* XCLocalSwiftPackageReference "GenerativeAIUIComponents" */,
);
productRefGroup = 8848C8302B0D04BC007B434F /* Products */;
projectDirPath = "";
Expand Down Expand Up @@ -385,7 +402,6 @@
86C1F4832BC726150026816F /* FunctionCallingScreen.swift in Sources */,
886F95DF2B17D5010036F07A /* BouncingDots.swift in Sources */,
86C1F4842BC726150026816F /* FunctionCallingViewModel.swift in Sources */,
886F95DE2B17D5010036F07A /* ChatMessage.swift in Sources */,
88263BF12B239C11008AB09B /* ErrorDetailsView.swift in Sources */,
8848C8352B0D04BC007B434F /* ContentView.swift in Sources */,
886F95D52B17BA010036F07A /* GenerateContentScreen.swift in Sources */,
Expand All @@ -398,7 +414,10 @@
886F95DB2B17BAEF0036F07A /* PhotoReasoningViewModel.swift in Sources */,
886F95E12B17D5010036F07A /* ConversationScreen.swift in Sources */,
88263BF02B239C09008AB09B /* ErrorView.swift in Sources */,
72DA044F2E385DF3004FED7D /* ChatMessage.swift in Sources */,
886F95D62B17BA010036F07A /* GenerateContentViewModel.swift in Sources */,
A5E8E3C92C3B4F388A7A4A19 /* FilterChipView.swift in Sources */,
A5E8E3CA2C3B4F388A7A4A1A /* SampleCardView.swift in Sources */,
AEE793DF2E256D3900708F02 /* GoogleSearchSuggestionView.swift in Sources */,
AEE793E02E256D3900708F02 /* GroundedResponseView.swift in Sources */,
);
Expand Down Expand Up @@ -609,6 +628,13 @@
};
/* End XCConfigurationList section */

/* Begin XCLocalSwiftPackageReference section */
7200F3062E3A054300CDC51C /* XCLocalSwiftPackageReference "GenerativeAIUIComponents" */ = {
isa = XCLocalSwiftPackageReference;
relativePath = GenerativeAIUIComponents;
};
/* End XCLocalSwiftPackageReference section */

/* Begin XCRemoteSwiftPackageReference section */
88209C212B0FBDF700F64795 /* XCRemoteSwiftPackageReference "swift-markdown-ui" */ = {
isa = XCRemoteSwiftPackageReference;
Expand Down Expand Up @@ -637,15 +663,15 @@
/* End XCRemoteSwiftPackageReference section */

/* Begin XCSwiftPackageProductDependency section */
7200F3072E3A054300CDC51C /* GenerativeAIUIComponents */ = {
isa = XCSwiftPackageProductDependency;
productName = GenerativeAIUIComponents;
};
886F95D72B17BA420036F07A /* MarkdownUI */ = {
isa = XCSwiftPackageProductDependency;
package = 88209C212B0FBDF700F64795 /* XCRemoteSwiftPackageReference "swift-markdown-ui" */;
productName = MarkdownUI;
};
886F95E22B17D6630036F07A /* GenerativeAIUIComponents */ = {
isa = XCSwiftPackageProductDependency;
productName = GenerativeAIUIComponents;
};
DE26D95E2DBB3E9F007E6668 /* FirebaseAI */ = {
isa = XCSwiftPackageProductDependency;
package = DEFECAAB2D7BB49700EF9621 /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */;
Expand Down
Loading