Skip to content

Commit de817cb

Browse files
authored
Add VertexAI Sample to repo (#1675)
1 parent ccc5c03 commit de817cb

File tree

40 files changed

+3161
-4
lines changed

40 files changed

+3161
-4
lines changed

Diff for: .github/workflows/vertexai.yml

+48
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
name: vertexai
2+
3+
on:
4+
pull_request:
5+
paths:
6+
- 'vertexai/**'
7+
schedule:
8+
# Run every day at 11pm (PST) - cron uses UTC times
9+
- cron: '0 7 * * *'
10+
workflow_dispatch:
11+
12+
concurrency:
13+
group: ${{ github.workflow }}-${{ github.head_ref || github.ref }}
14+
cancel-in-progress: true
15+
16+
env:
17+
SAMPLE: vertexai
18+
19+
jobs:
20+
spm:
21+
name: spm (Xcode ${{ matrix.xcode }} - ${{ matrix.os }})
22+
runs-on: macOS-15
23+
strategy:
24+
matrix:
25+
xcode: ["16.1"]
26+
os: [iOS]
27+
include:
28+
- os: iOS
29+
device: iPhone 16
30+
env:
31+
SETUP: vertexai
32+
SPM: true
33+
DIR: vertexai
34+
OS: ${{ matrix.os }}
35+
DEVICE: ${{ matrix.device }}
36+
TEST: false
37+
XCODE_VERSION: ${{ matrix.xcode }}
38+
DEVELOPER_DIR: /Applications/Xcode_${{ matrix.xcode }}.app/Contents/Developer
39+
steps:
40+
- name: Checkout
41+
uses: actions/checkout@master
42+
- name: Setup
43+
run: |
44+
gem install xcpretty
45+
- name: Placeholder GoogleService-Info.plist good enough for build only testing.
46+
run: cp ./mock-GoogleService-Info.plist ./vertexai/GoogleService-Info.plist
47+
- name: Build and Test SwiftUI (${{ matrix.os }})
48+
run: ./scripts/test.sh
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"colors" : [
3+
{
4+
"idiom" : "universal"
5+
}
6+
],
7+
"info" : {
8+
"author" : "xcode",
9+
"version" : 1
10+
}
11+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
{
2+
"images" : [
3+
{
4+
"idiom" : "universal",
5+
"platform" : "ios",
6+
"size" : "1024x1024"
7+
}
8+
],
9+
"info" : {
10+
"author" : "xcode",
11+
"version" : 1
12+
}
13+
}

Diff for: vertexai/ChatSample/Assets.xcassets/Contents.json

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"info" : {
3+
"author" : "xcode",
4+
"version" : 1
5+
}
6+
}

Diff for: vertexai/ChatSample/Models/ChatMessage.swift

+64
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
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 Foundation
16+
17+
enum Participant {
18+
case system
19+
case user
20+
}
21+
22+
struct ChatMessage: Identifiable, Equatable {
23+
let id = UUID().uuidString
24+
var message: String
25+
let participant: Participant
26+
var pending = false
27+
28+
static func pending(participant: Participant) -> ChatMessage {
29+
Self(message: "", participant: participant, pending: true)
30+
}
31+
}
32+
33+
extension ChatMessage {
34+
static var samples: [ChatMessage] = [
35+
.init(message: "Hello. What can I do for you today?", participant: .system),
36+
.init(message: "Show me a simple loop in Swift.", participant: .user),
37+
.init(message: """
38+
Sure, here is a simple loop in Swift:
39+
40+
# Example 1
41+
```
42+
for i in 1...5 {
43+
print("Hello, world!")
44+
}
45+
```
46+
47+
This loop will print the string "Hello, world!" five times. The for loop iterates over a range of numbers,
48+
in this case the numbers from 1 to 5. The variable i is assigned each number in the range, and the code inside the loop is executed.
49+
50+
**Here is another example of a simple loop in Swift:**
51+
```swift
52+
var sum = 0
53+
for i in 1...100 {
54+
sum += i
55+
}
56+
print("The sum of the numbers from 1 to 100 is \\(sum).")
57+
```
58+
59+
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.
60+
""", participant: .system),
61+
]
62+
63+
static var sample = samples[0]
64+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"info" : {
3+
"author" : "xcode",
4+
"version" : 1
5+
}
6+
}

Diff for: vertexai/ChatSample/Screens/ConversationScreen.swift

+129
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
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 FirebaseVertexAI
16+
import GenerativeAIUIComponents
17+
import SwiftUI
18+
19+
struct ConversationScreen: View {
20+
@EnvironmentObject
21+
var viewModel: ConversationViewModel
22+
23+
@State
24+
private var userPrompt = ""
25+
26+
enum FocusedField: Hashable {
27+
case message
28+
}
29+
30+
@FocusState
31+
var focusedField: FocusedField?
32+
33+
var body: some View {
34+
VStack {
35+
ScrollViewReader { scrollViewProxy in
36+
List {
37+
ForEach(viewModel.messages) { message in
38+
MessageView(message: message)
39+
}
40+
if let error = viewModel.error {
41+
ErrorView(error: error)
42+
.tag("errorView")
43+
}
44+
}
45+
.listStyle(.plain)
46+
.onChange(of: viewModel.messages, perform: { newValue in
47+
if viewModel.hasError {
48+
// wait for a short moment to make sure we can actually scroll to the bottom
49+
DispatchQueue.main.asyncAfter(deadline: .now() + 0.05) {
50+
withAnimation {
51+
scrollViewProxy.scrollTo("errorView", anchor: .bottom)
52+
}
53+
focusedField = .message
54+
}
55+
} else {
56+
guard let lastMessage = viewModel.messages.last else { return }
57+
58+
// wait for a short moment to make sure we can actually scroll to the bottom
59+
DispatchQueue.main.asyncAfter(deadline: .now() + 0.05) {
60+
withAnimation {
61+
scrollViewProxy.scrollTo(lastMessage.id, anchor: .bottom)
62+
}
63+
focusedField = .message
64+
}
65+
}
66+
})
67+
}
68+
InputField("Message...", text: $userPrompt) {
69+
Image(systemName: viewModel.busy ? "stop.circle.fill" : "arrow.up.circle.fill")
70+
.font(.title)
71+
}
72+
.focused($focusedField, equals: .message)
73+
.onSubmit { sendOrStop() }
74+
}
75+
.toolbar {
76+
ToolbarItem(placement: .primaryAction) {
77+
Button(action: newChat) {
78+
Image(systemName: "square.and.pencil")
79+
}
80+
}
81+
}
82+
.navigationTitle("Chat sample")
83+
.onAppear {
84+
focusedField = .message
85+
}
86+
}
87+
88+
private func sendMessage() {
89+
Task {
90+
let prompt = userPrompt
91+
userPrompt = ""
92+
await viewModel.sendMessage(prompt, streaming: true)
93+
}
94+
}
95+
96+
private func sendOrStop() {
97+
focusedField = nil
98+
99+
if viewModel.busy {
100+
viewModel.stop()
101+
} else {
102+
sendMessage()
103+
}
104+
}
105+
106+
private func newChat() {
107+
viewModel.startNewChat()
108+
}
109+
}
110+
111+
struct ConversationScreen_Previews: PreviewProvider {
112+
struct ContainerView: View {
113+
@StateObject var viewModel = ConversationViewModel()
114+
115+
var body: some View {
116+
ConversationScreen()
117+
.environmentObject(viewModel)
118+
.onAppear {
119+
viewModel.messages = ChatMessage.samples
120+
}
121+
}
122+
}
123+
124+
static var previews: some View {
125+
NavigationStack {
126+
ConversationScreen()
127+
}
128+
}
129+
}

0 commit comments

Comments
 (0)