I have added the Gemini ChatBot extension to a Firebase project.
I have added the profile!.uid
to the document via send message function so that I can only retrieve the users ChatDocuments
.
func sendMessage(_ message: String) {
guard let profileId = profile?.uid else {
print("Profile ID not found")
return
}
let data: [String: Any] = [
"prompt": message,
"userID": profileId,
]
db.collection(collectionPath).addDocument(data: data) { error in
if let error = error {
print("Error adding document: \(error)")
} else {
print("Document added for userID: \(profileId)")
}
}
}
At this point, the app successfully adds a document to the FireStore and the extension adds a response.
The response is the show in the UI using the following function.
func fetchMessages() {
print("Fetch messages called for userID: \(profile!.uid)")
db.collection(collectionPath)
.whereField("userID", isEqualTo: profile!.uid)
.order(by: "createTime", descending: false)
.addSnapshotListener { [weak self] querySnapshot, error in
guard let self else { return }
guard let documents = querySnapshot?.documents else {
print("No documents found")
return
}
self.messages = documents.compactMap { queryDocumentSnapshot -> [ChatMessage]? in
do {
let document = try queryDocumentSnapshot.data(as: ChatDocument.self)
let prompt = ChatMessage(text: document.prompt, isUser: true, state: .COMPLETED)
let response = ChatMessage(text: document.response ?? "", isUser: false, state: document.status.chatState)
print("RESPONSE FROM DOCUMENTS")
print("fetched \(response)")
return [prompt, response]
} catch {
print(error.localizedDescription)
return nil
}
}.flatMap { $0 }
}
}
However when adding the following line, the documents are not fetched.
.whereField("userID", isEqualTo: profile!.uid)
Chat Document
struct ChatDocument: Codable {
let createTime: Timestamp
let prompt: String
let response: String?
let status: Status
let userID: String
struct Status: Codable {
let startTime: Timestamp?
let completeTime: Timestamp?
let updateTime: Timestamp
let state: String
let error: String?
var chatState: ChatState {
return ChatState(rawValue: state) ?? .PROCESSING
}
}
}
struct ChatMessage: Hashable {
@ServerTimestamp var createdTime: Timestamp?
@DocumentID var uid: String?
private(set) var id: UUID = .init()
var text: String?
var isUser: Bool
var state: ChatState = .PROCESSING
var message: String {
switch state {
case .COMPLETED:
return text ?? ""
case .ERROR:
return "Something went wrong. Please try again."
case .PROCESSING:
return "..."
}
}
}
enum ChatState: String, Codable {
case COMPLETED
case ERROR
case PROCESSING
}
FireStore:
Interestingly the following removes all documents for the profile.uid
and uses the same .whereField("userID", isEqualTo: profile!.uid)
func removeAllMessages() async {
do {
let querySnapshot = try await db.collection(collectionPath)
.whereField("userID", isEqualTo: profile!.uid)
.getDocuments()
for document in querySnapshot.documents {
do {
try await db.collection(collectionPath).document(document.documentID).delete()
self.messages.removeAll()
print("Document with ID \(document.documentID) successfully removed!")
} catch {
print("Error removing document with ID \(document.documentID): \(error)")
}
}
} catch {
print("Error querying documents for profileId \(profile!.uid): \(error)")
}
}
Most likely you're missing composite index that is required for this combination:
.whereField("userID", isEqualTo: profile!.uid)
.order(by: "createTime", descending: false)
The simplest solution is to catch-and-log any error that is thrown by the code, as that'll include a URL for the Firestore console to create the link - with all the fields already filled in.
To learn more about Firestore's index logic, read the documentation on index types, on composite indexes, and on creating the required indexes.