Files
firebase-ios-sdk/FirebaseVertexAI/Sample/FunctionCallingSample/Screens/FunctionCallingScreen.swift
T

133 lines
3.6 KiB
Swift
Raw Normal View History

// Copyright 2023 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import FirebaseVertexAI
import GenerativeAIUIComponents
import SwiftUI
struct FunctionCallingScreen: View {
@EnvironmentObject
var viewModel: FunctionCallingViewModel
@State
private var userPrompt = "What is 100 Euros in U.S. Dollars?"
enum FocusedField: Hashable {
case message
}
@FocusState
var focusedField: FocusedField?
var body: some View {
VStack {
ScrollViewReader { scrollViewProxy in
List {
Text("Interact with a currency conversion API using function calling in Gemini.")
ForEach(viewModel.messages) { message in
MessageView(message: message)
}
if let error = viewModel.error {
ErrorView(error: error)
.tag("errorView")
}
}
.listStyle(.plain)
.onChange(of: viewModel.messages, perform: { newValue in
if viewModel.hasError {
// Wait for a short moment to make sure we can actually scroll to the bottom.
DispatchQueue.main.asyncAfter(deadline: .now() + 0.05) {
withAnimation {
scrollViewProxy.scrollTo("errorView", anchor: .bottom)
}
focusedField = .message
}
} else {
guard let lastMessage = viewModel.messages.last else { return }
// Wait for a short moment to make sure we can actually scroll to the bottom.
DispatchQueue.main.asyncAfter(deadline: .now() + 0.05) {
withAnimation {
scrollViewProxy.scrollTo(lastMessage.id, anchor: .bottom)
}
focusedField = .message
}
}
})
.onTapGesture {
focusedField = nil
}
}
InputField("Message...", text: $userPrompt) {
Image(systemName: viewModel.busy ? "stop.circle.fill" : "arrow.up.circle.fill")
.font(.title)
}
.focused($focusedField, equals: .message)
.onSubmit { sendOrStop() }
}
.toolbar {
ToolbarItem(placement: .primaryAction) {
Button(action: newChat) {
Image(systemName: "square.and.pencil")
}
}
}
.navigationTitle("Function Calling")
.onAppear {
focusedField = .message
}
}
private func sendMessage() {
Task {
let prompt = userPrompt
userPrompt = ""
await viewModel.sendMessage(prompt, streaming: true)
}
}
private func sendOrStop() {
if viewModel.busy {
viewModel.stop()
} else {
sendMessage()
}
}
private func newChat() {
Task {
await viewModel.startNewChat()
}
}
}
struct FunctionCallingScreen_Previews: PreviewProvider {
struct ContainerView: View {
@EnvironmentObject
var viewModel: FunctionCallingViewModel
var body: some View {
FunctionCallingScreen()
.onAppear {
viewModel.messages = ChatMessage.samples
}
}
}
static var previews: some View {
NavigationStack {
FunctionCallingScreen().environmentObject(FunctionCallingViewModel())
}
}
}