Skip to content

Commit 62ab3f7

Browse files
committed
Merge branch 'main' into nc/quickstarts
2 parents 17dd7b9 + 0f1df73 commit 62ab3f7

File tree

7 files changed

+432
-19
lines changed

7 files changed

+432
-19
lines changed

firebaseai/FirebaseAIExample.xcodeproj/project.pbxproj

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,14 @@
6060
AEE793DF2E256D3900708F02 /* GoogleSearchSuggestionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = AEE793DC2E256D3900708F02 /* GoogleSearchSuggestionView.swift */; };
6161
AEE793E02E256D3900708F02 /* GroundedResponseView.swift in Sources */ = {isa = PBXBuildFile; fileRef = AEE793DD2E256D3900708F02 /* GroundedResponseView.swift */; };
6262
DE26D95F2DBB3E9F007E6668 /* FirebaseAI in Frameworks */ = {isa = PBXBuildFile; productRef = DE26D95E2DBB3E9F007E6668 /* FirebaseAI */; };
63+
DE907A812EAAE53E00AE56CE /* GenerateContentFromTemplateScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = DE907A802EAAE53E00AE56CE /* GenerateContentFromTemplateScreen.swift */; };
64+
DE907A822EAAE53E00AE56CE /* GenerateContentFromTemplateScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = DE907A802EAAE53E00AE56CE /* GenerateContentFromTemplateScreen.swift */; };
65+
DE907A842EAAE55600AE56CE /* GenerateContentFromTemplateViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DE907A832EAAE55600AE56CE /* GenerateContentFromTemplateViewModel.swift */; };
66+
DE907A852EAAE55600AE56CE /* GenerateContentFromTemplateViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DE907A832EAAE55600AE56CE /* GenerateContentFromTemplateViewModel.swift */; };
67+
DE907A882EAAEBCC00AE56CE /* ImagenFromTemplateViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DE907A872EAAEBCC00AE56CE /* ImagenFromTemplateViewModel.swift */; };
68+
DE907A892EAAEBCC00AE56CE /* ImagenFromTemplateScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = DE907A862EAAEBCC00AE56CE /* ImagenFromTemplateScreen.swift */; };
69+
DE907A8A2EAAEBCC00AE56CE /* ImagenFromTemplateViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DE907A872EAAEBCC00AE56CE /* ImagenFromTemplateViewModel.swift */; };
70+
DE907A8B2EAAEBCC00AE56CE /* ImagenFromTemplateScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = DE907A862EAAEBCC00AE56CE /* ImagenFromTemplateScreen.swift */; };
6371
DEFECAA92D7B4CCD00EF9621 /* ImagenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DEFECAA72D7B4CCD00EF9621 /* ImagenViewModel.swift */; };
6472
DEFECAAA2D7B4CCD00EF9621 /* ImagenScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = DEFECAA62D7B4CCD00EF9621 /* ImagenScreen.swift */; };
6573
/* End PBXBuildFile section */
@@ -94,6 +102,10 @@
94102
88E10F5C2B11135000C08E95 /* BouncingDots.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BouncingDots.swift; sourceTree = "<group>"; };
95103
AEE793DC2E256D3900708F02 /* GoogleSearchSuggestionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GoogleSearchSuggestionView.swift; sourceTree = "<group>"; };
96104
AEE793DD2E256D3900708F02 /* GroundedResponseView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroundedResponseView.swift; sourceTree = "<group>"; };
105+
DE907A802EAAE53E00AE56CE /* GenerateContentFromTemplateScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GenerateContentFromTemplateScreen.swift; sourceTree = "<group>"; };
106+
DE907A832EAAE55600AE56CE /* GenerateContentFromTemplateViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GenerateContentFromTemplateViewModel.swift; sourceTree = "<group>"; };
107+
DE907A862EAAEBCC00AE56CE /* ImagenFromTemplateScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImagenFromTemplateScreen.swift; sourceTree = "<group>"; };
108+
DE907A872EAAEBCC00AE56CE /* ImagenFromTemplateViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImagenFromTemplateViewModel.swift; sourceTree = "<group>"; };
97109
DEFECAA62D7B4CCD00EF9621 /* ImagenScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImagenScreen.swift; sourceTree = "<group>"; };
98110
DEFECAA72D7B4CCD00EF9621 /* ImagenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImagenViewModel.swift; sourceTree = "<group>"; };
99111
/* End PBXFileReference section */
@@ -171,6 +183,7 @@
171183
88209C1A2B0FBDC300F64795 /* Screens */ = {
172184
isa = PBXGroup;
173185
children = (
186+
DE907A802EAAE53E00AE56CE /* GenerateContentFromTemplateScreen.swift */,
174187
88209C1B2B0FBDC300F64795 /* GenerateContentScreen.swift */,
175188
);
176189
path = Screens;
@@ -179,6 +192,7 @@
179192
88209C1C2B0FBDC300F64795 /* ViewModels */ = {
180193
isa = PBXGroup;
181194
children = (
195+
DE907A832EAAE55600AE56CE /* GenerateContentFromTemplateViewModel.swift */,
182196
88209C1D2B0FBDC300F64795 /* GenerateContentViewModel.swift */,
183197
);
184198
path = ViewModels;
@@ -342,6 +356,8 @@
342356
DEFECAA82D7B4CCD00EF9621 /* ImagenScreen */ = {
343357
isa = PBXGroup;
344358
children = (
359+
DE907A862EAAEBCC00AE56CE /* ImagenFromTemplateScreen.swift */,
360+
DE907A872EAAEBCC00AE56CE /* ImagenFromTemplateViewModel.swift */,
345361
DEFECAA62D7B4CCD00EF9621 /* ImagenScreen.swift */,
346362
DEFECAA72D7B4CCD00EF9621 /* ImagenViewModel.swift */,
347363
);
@@ -468,6 +484,8 @@
468484
isa = PBXSourcesBuildPhase;
469485
buildActionMask = 2147483647;
470486
files = (
487+
DE907A852EAAE55600AE56CE /* GenerateContentFromTemplateViewModel.swift in Sources */,
488+
DE907A812EAAE53E00AE56CE /* GenerateContentFromTemplateScreen.swift in Sources */,
471489
86BB55EA2E8B2D6D0054B8B5 /* FunctionCallingScreen.swift in Sources */,
472490
86BB55EB2E8B2D6D0054B8B5 /* BouncingDots.swift in Sources */,
473491
86BB55EC2E8B2D6D0054B8B5 /* FunctionCallingViewModel.swift in Sources */,
@@ -481,6 +499,8 @@
481499
86BB55F42E8B2D6D0054B8B5 /* PhotoReasoningScreen.swift in Sources */,
482500
86BB55F52E8B2D6D0054B8B5 /* ImagenViewModel.swift in Sources */,
483501
86BB55F62E8B2D6D0054B8B5 /* ImagenScreen.swift in Sources */,
502+
DE907A882EAAEBCC00AE56CE /* ImagenFromTemplateViewModel.swift in Sources */,
503+
DE907A892EAAEBCC00AE56CE /* ImagenFromTemplateScreen.swift in Sources */,
484504
86BB55F72E8B2D6D0054B8B5 /* PhotoReasoningViewModel.swift in Sources */,
485505
86BB55F82E8B2D6D0054B8B5 /* ConversationScreen.swift in Sources */,
486506
86BB55F92E8B2D6D0054B8B5 /* ErrorView.swift in Sources */,
@@ -494,6 +514,8 @@
494514
isa = PBXSourcesBuildPhase;
495515
buildActionMask = 2147483647;
496516
files = (
517+
DE907A842EAAE55600AE56CE /* GenerateContentFromTemplateViewModel.swift in Sources */,
518+
DE907A822EAAE53E00AE56CE /* GenerateContentFromTemplateScreen.swift in Sources */,
497519
86C1F4832BC726150026816F /* FunctionCallingScreen.swift in Sources */,
498520
886F95DF2B17D5010036F07A /* BouncingDots.swift in Sources */,
499521
86C1F4842BC726150026816F /* FunctionCallingViewModel.swift in Sources */,
@@ -507,6 +529,8 @@
507529
886F95DC2B17BAEF0036F07A /* PhotoReasoningScreen.swift in Sources */,
508530
DEFECAA92D7B4CCD00EF9621 /* ImagenViewModel.swift in Sources */,
509531
DEFECAAA2D7B4CCD00EF9621 /* ImagenScreen.swift in Sources */,
532+
DE907A8A2EAAEBCC00AE56CE /* ImagenFromTemplateViewModel.swift in Sources */,
533+
DE907A8B2EAAEBCC00AE56CE /* ImagenFromTemplateScreen.swift in Sources */,
510534
886F95DB2B17BAEF0036F07A /* PhotoReasoningViewModel.swift in Sources */,
511535
886F95E12B17D5010036F07A /* ConversationScreen.swift in Sources */,
512536
88263BF02B239C09008AB09B /* ErrorView.swift in Sources */,
@@ -828,7 +852,7 @@
828852
repositoryURL = "https://github.com/firebase/firebase-ios-sdk.git";
829853
requirement = {
830854
kind = upToNextMajorVersion;
831-
minimumVersion = 12.0.0;
855+
minimumVersion = 12.6.0;
832856
};
833857
};
834858
/* End XCRemoteSwiftPackageReference section */

firebaseai/FirebaseAIExample/ContentView.swift

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,11 @@ struct ContentView: View {
5555
} label: {
5656
Label("Generate Content", systemImage: "doc.text")
5757
}
58+
NavigationLink {
59+
GenerateContentFromTemplateScreen(firebaseService: firebaseService)
60+
} label: {
61+
Label("Generate Content from Template", systemImage: "doc.text.fill")
62+
}
5863
NavigationLink {
5964
PhotoReasoningScreen(firebaseService: firebaseService)
6065
} label: {
@@ -84,6 +89,11 @@ struct ContentView: View {
8489
} label: {
8590
Label("Imagen", systemImage: "camera.circle")
8691
}
92+
NavigationLink {
93+
ImagenFromTemplateScreen(firebaseService: firebaseService)
94+
} label: {
95+
Label("Imagen from Template", systemImage: "camera.circle.fill")
96+
}
8797
}
8898
}
8999
.navigationTitle("Generative AI Examples")
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
// Copyright 2025 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 MarkdownUI
16+
import SwiftUI
17+
#if canImport(FirebaseAILogic)
18+
import FirebaseAILogic
19+
#else
20+
import FirebaseAI
21+
#endif
22+
import GenerativeAIUIComponents
23+
24+
struct GenerateContentFromTemplateScreen: View {
25+
let firebaseService: FirebaseAI
26+
@StateObject var viewModel: GenerateContentFromTemplateViewModel
27+
@State var userInput = ""
28+
29+
init(firebaseService: FirebaseAI) {
30+
self.firebaseService = firebaseService
31+
_viewModel =
32+
StateObject(
33+
wrappedValue: GenerateContentFromTemplateViewModel(firebaseService: firebaseService)
34+
)
35+
}
36+
37+
enum FocusedField: Hashable {
38+
case message
39+
}
40+
41+
@FocusState
42+
var focusedField: FocusedField?
43+
44+
var body: some View {
45+
VStack {
46+
VStack(alignment: .leading) {
47+
Text("Enter your name, then tap on _Go_ to run generateContent from template on it.")
48+
.padding(.horizontal, 6)
49+
InputField("Enter your name", text: $userInput) {
50+
Text("Go")
51+
}
52+
.focused($focusedField, equals: .message)
53+
.onSubmit { onGenerateContentTapped() }
54+
}
55+
.padding(.horizontal, 16)
56+
57+
List {
58+
HStack(alignment: .top) {
59+
if viewModel.inProgress {
60+
ProgressView()
61+
} else {
62+
Image(systemName: "cloud.circle.fill")
63+
.font(.title2)
64+
}
65+
66+
Markdown("\(viewModel.outputText)")
67+
}
68+
.listRowSeparator(.hidden)
69+
}
70+
.listStyle(.plain)
71+
}
72+
.onTapGesture {
73+
focusedField = nil
74+
}
75+
.navigationTitle("Template Generate Content")
76+
}
77+
78+
private func onGenerateContentTapped() {
79+
focusedField = nil
80+
81+
Task {
82+
await viewModel.generateContentFromTemplate(name: userInput)
83+
}
84+
}
85+
}
86+
87+
#Preview {
88+
NavigationStack {
89+
GenerateContentFromTemplateScreen(firebaseService: FirebaseAI.firebaseAI())
90+
}
91+
}
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
// Copyright 2025 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+
#if canImport(FirebaseAILogic)
16+
import FirebaseAILogic
17+
#else
18+
import FirebaseAI
19+
#endif
20+
import Foundation
21+
import OSLog
22+
23+
// Template Details
24+
//
25+
// Configuration
26+
//
27+
// input:
28+
// default:
29+
// language: english
30+
// schema:
31+
// name: string
32+
// language?: string
33+
//
34+
// Prompt and system instructions
35+
//
36+
// {{role "system"}}
37+
// The user's name is {{name}}. They prefer to communicate in {{language}}.
38+
// {{role "user"}}
39+
// Say hello.
40+
//
41+
42+
@MainActor
43+
class GenerateContentFromTemplateViewModel: ObservableObject {
44+
private var logger = Logger(
45+
subsystem: Bundle.main.bundleIdentifier ?? "com.google.firebase.quickstart.FirebaseAIExample",
46+
category: "generative-ai"
47+
)
48+
49+
@Published
50+
var outputText = ""
51+
52+
@Published
53+
var errorMessage: String?
54+
55+
@Published
56+
var inProgress = false
57+
58+
private var model: TemplateGenerativeModel?
59+
60+
init(firebaseService: FirebaseAI) {
61+
model = firebaseService.templateGenerativeModel()
62+
}
63+
64+
func generateContentFromTemplate(name: String) async {
65+
defer {
66+
inProgress = false
67+
}
68+
guard let model else {
69+
return
70+
}
71+
72+
do {
73+
inProgress = true
74+
errorMessage = nil
75+
outputText = ""
76+
77+
let response = try await model.generateContent(
78+
templateID: "apple-qs-greeting",
79+
inputs: [
80+
"name": name,
81+
"language": "Spanish",
82+
]
83+
)
84+
if let text = response.text {
85+
outputText = text
86+
}
87+
} catch {
88+
logger.error("\(error.localizedDescription)")
89+
errorMessage = error.localizedDescription
90+
}
91+
}
92+
}

0 commit comments

Comments
 (0)