Skip to content

Commit 0900fc9

Browse files
YoungHypopeterfriesegemini-code-assist[bot]
committed
[FirebaseAI ] Integrate conversationkit (#1745)
* integrate conversationkit to firebaseai * refract functioncalling logic * remove fatalError * change preview for screens * ♻️ Use ConversationKit * ♻️ Refactor error handling * ✨ Bring back pre-filled user messages * 🧹Cleanup * ⬆️ Use latest ConversationKit version * fix style check * add errordetailview for imagenexample * ✏️ Fix typo Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> * ✏️ Fix typo Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> * ✏️ Fix typo Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> * ✏️ Fix typo Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> * fix style and change ci --------- Co-authored-by: Peter Friese <[email protected]> Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
1 parent 38d57cb commit 0900fc9

File tree

21 files changed

+575
-721
lines changed

21 files changed

+575
-721
lines changed

firebaseai/FirebaseAIExample.xcodeproj/project.pbxproj

Lines changed: 85 additions & 76 deletions
Large diffs are not rendered by default.

firebaseai/FirebaseAIExample/ChatExample/Models/ChatMessage.swift

Lines changed: 53 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -18,35 +18,65 @@
1818
import FirebaseAI
1919
#endif
2020
import Foundation
21+
import ConversationKit
2122

22-
enum Participant {
23-
case system
24-
case user
25-
}
23+
public struct ChatMessage: Message {
24+
public let id: UUID = .init()
25+
public var content: String?
26+
public let imageURL: String?
27+
public let participant: Participant
28+
public let error: (any Error)?
29+
public var pending = false
30+
public var groundingMetadata: GroundingMetadata?
2631

27-
struct ChatMessage: Identifiable, Equatable {
28-
let id = UUID().uuidString
29-
var message: String
30-
var groundingMetadata: GroundingMetadata?
31-
let participant: Participant
32-
var pending = false
32+
public init(content: String? = nil, imageURL: String? = nil, participant: Participant,
33+
error: (any Error)? = nil, pending: Bool = false) {
34+
self.content = content
35+
self.imageURL = imageURL
36+
self.participant = participant
37+
self.error = error
38+
self.pending = pending
39+
}
3340

34-
static func pending(participant: Participant) -> ChatMessage {
35-
Self(message: "", participant: participant, pending: true)
41+
// Protocol-required initializer
42+
public init(content: String?, imageURL: String?, participant: Participant) {
43+
self.content = content
44+
self.imageURL = imageURL
45+
self.participant = participant
46+
error = nil
3647
}
48+
}
3749

38-
// TODO(andrewheard): Add Equatable conformance to GroundingMetadata and remove this
39-
static func == (lhs: ChatMessage, rhs: ChatMessage) -> Bool {
40-
lhs.id == rhs.id && lhs.message == rhs.message && lhs.participant == rhs.participant && lhs
41-
.pending == rhs.pending
50+
extension ChatMessage {
51+
public static func pending(participant: Participant) -> ChatMessage {
52+
Self(content: "", participant: participant, pending: true)
4253
}
4354
}
4455

56+
// Implement Equatable and Hashable for ChatMessage (ignore error)
4557
extension ChatMessage {
58+
public static func == (lhs: ChatMessage, rhs: ChatMessage) -> Bool {
59+
lhs.id == rhs.id &&
60+
lhs.content == rhs.content &&
61+
lhs.imageURL == rhs.imageURL &&
62+
lhs.participant == rhs.participant
63+
// intentionally ignore `error`
64+
}
65+
66+
public func hash(into hasher: inout Hasher) {
67+
hasher.combine(id)
68+
hasher.combine(content)
69+
hasher.combine(imageURL)
70+
hasher.combine(participant)
71+
// intentionally ignore `error`
72+
}
73+
}
74+
75+
public extension ChatMessage {
4676
static var samples: [ChatMessage] = [
47-
.init(message: "Hello. What can I do for you today?", participant: .system),
48-
.init(message: "Show me a simple loop in Swift.", participant: .user),
49-
.init(message: """
77+
.init(content: "Hello. What can I do for you today?", participant: .other),
78+
.init(content: "Show me a simple loop in Swift.", participant: .user),
79+
.init(content: """
5080
Sure, here is a simple loop in Swift:
5181
5282
# Example 1
@@ -69,23 +99,23 @@ extension ChatMessage {
6999
```
70100
71101
This loop calculates the sum of the numbers from 1 to 100. The variable sum is initialized to 0, and then the for loop iterates over the range of numbers from 1 to 100. The variable i is assigned each number in the range, and the value of i is added to the sum variable. After the loop has finished executing, the value of sum is printed to the console.
72-
""", participant: .system),
102+
""", participant: .other),
73103
]
74104

75105
static var sample = samples[0]
76106
}
77107

78-
extension ChatMessage {
108+
public extension ChatMessage {
79109
static func from(_ modelContent: ModelContent) -> ChatMessage? {
80110
// TODO: add non-text parts to message when multi-model support is added
81111
let text = modelContent.parts.compactMap { ($0 as? TextPart)?.text }.joined()
82112
guard !text.isEmpty else {
83113
return nil
84114
}
85115

86-
let participant: Participant = (modelContent.role == "user") ? .user : .system
116+
let participant: Participant = (modelContent.role == "user") ? .user : .other
87117

88-
return ChatMessage(message: text, participant: participant)
118+
return ChatMessage(content: text, participant: participant)
89119
}
90120

91121
static func from(_ modelContents: [ModelContent]) -> [ChatMessage] {
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
// Copyright 2023 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
import FirebaseAI
16+
import SwiftUI
17+
import ConversationKit
18+
19+
struct ChatScreen: View {
20+
let firebaseService: FirebaseAI
21+
@StateObject var viewModel: ChatViewModel
22+
23+
init(firebaseService: FirebaseAI, sample: Sample? = nil) {
24+
self.firebaseService = firebaseService
25+
_viewModel =
26+
StateObject(wrappedValue: ChatViewModel(firebaseService: firebaseService,
27+
sample: sample))
28+
}
29+
30+
var body: some View {
31+
NavigationStack {
32+
ConversationView(messages: $viewModel.messages,
33+
userPrompt: viewModel.initialPrompt) { message in
34+
MessageView(message: message)
35+
}
36+
.disableAttachments()
37+
.onSendMessage { message in
38+
Task {
39+
await viewModel.sendMessage(message.content ?? "", streaming: true)
40+
}
41+
}
42+
.onError { error in
43+
viewModel.presentErrorDetails = true
44+
}
45+
.sheet(isPresented: $viewModel.presentErrorDetails) {
46+
if let error = viewModel.error {
47+
ErrorDetailsView(error: error)
48+
}
49+
}
50+
.toolbar {
51+
ToolbarItem(placement: .primaryAction) {
52+
Button(action: newChat) {
53+
Image(systemName: "square.and.pencil")
54+
}
55+
}
56+
}
57+
.navigationTitle(viewModel.title)
58+
.navigationBarTitleDisplayMode(.inline)
59+
}
60+
}
61+
62+
private func newChat() {
63+
viewModel.startNewChat()
64+
}
65+
}
66+
67+
#Preview {
68+
ChatScreen(firebaseService: FirebaseAI.firebaseAI())
69+
}

firebaseai/FirebaseAIExample/ChatExample/Screens/ConversationScreen.swift

Lines changed: 0 additions & 149 deletions
This file was deleted.

0 commit comments

Comments
 (0)