Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -58,3 +58,4 @@ GoogleService-Info.plist
google-services.json
.idea
local.properties
/vertex-ai-friendly-meals/apple/service-accounts
475 changes: 475 additions & 0 deletions vertex-ai-friendly-meals/apple/FriendlyMeals.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1630"
version = "1.7">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES"
buildArchitectures = "Automatic">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "8835AEDC2DAEA78E0047378F"
BuildableName = "FriendlyMeals.app"
BlueprintName = "FriendlyMeals"
ReferencedContainer = "container:FriendlyMeals.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES"
shouldAutocreateTestPlan = "YES">
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "8835AEDC2DAEA78E0047378F"
BuildableName = "FriendlyMeals.app"
BlueprintName = "FriendlyMeals"
ReferencedContainer = "container:FriendlyMeals.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "8835AEDC2DAEA78E0047378F"
BuildableName = "FriendlyMeals.app"
BlueprintName = "FriendlyMeals"
ReferencedContainer = "container:FriendlyMeals.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"colors" : [
{
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
{
"images" : [
{
"idiom" : "universal",
"platform" : "ios",
"size" : "1024x1024"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"idiom" : "universal",
"platform" : "ios",
"size" : "1024x1024"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "tinted"
}
],
"idiom" : "universal",
"platform" : "ios",
"size" : "1024x1024"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"info" : {
"author" : "xcode",
"version" : 1
}
}
90 changes: 90 additions & 0 deletions vertex-ai-friendly-meals/apple/FriendlyMeals/ContentView.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
//
// ContentView.swift
// FriendlyMeals
//
// Created by Peter Friese on 15.04.25.
//
// 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 SwiftUI
import IdentityKit

struct AuthenticationToolbarContent: ViewModifier {
@State private var showAuthSheet = false
@Environment(AuthenticationService.self) private var authService

func body(content: Content) -> some View {
content
.toolbar {
ToolbarItem(placement: .topBarTrailing) {
Button {
if authService.isAuthenticated {
Task {
try await authService.signOut()
}
} else {
showAuthSheet = true
}
} label: {
Image(systemName: authService.isAuthenticated ? "person.fill" : "person")
}
}
}
.sheet(isPresented: $showAuthSheet) {
AuthenticationScreen()
.authenticationProviders([.email, .apple])
}
}
}

extension View {
func withAuthenticationToolbar() -> some View {
modifier(AuthenticationToolbarContent())
}
}

@MainActor
struct ContentView {
}

extension ContentView: View {
var body: some View {
TabView {
RecipeList()
.tabItem {
Label("Recipes", systemImage: "fork.knife")
}

RecipeGenerationView()
.tabItem {
Label("Inspire Me", systemImage: "sparkles")
}

VisionRecipeGenerationView()
.tabItem {
Label("Vision", systemImage: "photo")
}

PersonalChefView()
.tabItem {
Label("Personal Chef", systemImage: "bubble.left.and.bubble.right")
}
}
.withAuthenticationToolbar()
}
}

#Preview {
ContentView()
.environment(AuthenticationService.shared)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import SwiftUI
import ConversationKit

@MainActor
struct PersonalChefView {
@State private var messages: [Message] = [
Message(content: "Hello! I'm your personal chef assistant. I can help you find recipes, suggest meal ideas, and answer cooking questions. What would you like to cook today?", participant: .other)
]

private func handleUserMessage(_ message: Message) {
// Add loading state
var responseMessage = Message(content: "Thinking...", participant: .other)
messages.append(responseMessage)

// Simulate chef's response
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
responseMessage.content = "I'd love to help you with that! Could you tell me about any dietary restrictions or preferences you have?"
if let index = messages.firstIndex(where: { $0.id == responseMessage.id }) {
messages[index] = responseMessage
}
}
}
}

extension PersonalChefView: View {
var body: some View {
NavigationStack {
ConversationView(messages: $messages)
.onSendMessage { userMessage in
handleUserMessage(userMessage)
}
.navigationTitle("Personal Chef")
.navigationBarTitleDisplayMode(.inline)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import SwiftUI
import NukeUI

@MainActor
struct RecipeDetailsView {
let recipe: Recipe
}

extension RecipeDetailsView: View {
var body: some View {
ScrollView {
VStack(alignment: .leading, spacing: 16) {
if let generatedImage = recipe.generatedImage,
let uiImage = UIImage(data: generatedImage) {
Image(uiImage: uiImage)
.resizable()
.aspectRatio(contentMode: .fill)
.frame(maxWidth: .infinity)
.frame(height: 200)
.clipped()
} else {
LazyImage(url: recipe.imageURL) { state in
if let image = state.image {
image
.resizable()
.aspectRatio(contentMode: .fill)
.frame(maxWidth: .infinity)
.frame(height: 200)
.clipped()
} else {
ProgressView()
.frame(maxWidth: .infinity)
.frame(height: 200)
}
}
}

VStack(alignment: .leading, spacing: 8) {
Text(recipe.title)
.font(.title)

Text("\(recipe.cookingTimeInMinutes) minutes • \(recipe.cuisine.rawValue)")
.foregroundColor(.secondary)

Text(recipe.description)
.padding(.top)

Text("Ingredients")
.font(.headline)
.padding(.top)

ForEach(recipe.ingredients, id: \.self) { ingredient in
Text("• \(ingredient)")
}

Text("Instructions")
.font(.headline)
.padding(.top)

ForEach(Array(recipe.instructions.enumerated()), id: \.element) { index, instruction in
Text("\(index + 1). \(instruction)")
.padding(.bottom, 4)
}
}
.padding()
}
}
.navigationBarTitleDisplayMode(.inline)
}
}

#Preview {
NavigationStack {
RecipeDetailsView(recipe: Recipe.mock)
}
}
Loading