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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions Stanford360.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,7 @@
/* Begin PBXFileSystemSynchronizedRootGroup section */
45E2F0FC2D82132B0097C339 /* HydrationTests */ = {isa = PBXFileSystemSynchronizedRootGroup; explicitFileTypes = {}; explicitFolders = (); path = HydrationTests; sourceTree = "<group>"; };
45E2F12D2D8291E30097C339 /* HydrationUITests */ = {isa = PBXFileSystemSynchronizedRootGroup; explicitFileTypes = {}; explicitFolders = (); path = HydrationUITests; sourceTree = "<group>"; };
4D490EDC2D86A78200C7E06F /* ProteinUITests */ = {isa = PBXFileSystemSynchronizedRootGroup; explicitFileTypes = {}; explicitFolders = (); path = ProteinUITests; sourceTree = "<group>"; };
4DB472D62D80459F005E895E /* ProteinTest */ = {isa = PBXFileSystemSynchronizedRootGroup; explicitFileTypes = {}; explicitFolders = (); path = ProteinTest; sourceTree = "<group>"; };
4F8EA0B92D680A4400A94137 /* Activity */ = {isa = PBXFileSystemSynchronizedRootGroup; explicitFileTypes = {}; explicitFolders = (); path = Activity; sourceTree = "<group>"; };
4FF18DDD2D5FAB5E00E13832 /* ActivityTests */ = {isa = PBXFileSystemSynchronizedRootGroup; explicitFileTypes = {}; explicitFolders = (); path = ActivityTests; sourceTree = "<group>"; };
Expand Down Expand Up @@ -368,6 +369,7 @@
653A256A28338800005D4D48 /* Stanford360UITests */ = {
isa = PBXGroup;
children = (
4D490EDC2D86A78200C7E06F /* ProteinUITests */,
45E2F12D2D8291E30097C339 /* HydrationUITests */,
2F4E237D2989A2FE0013F3D9 /* OnboardingTests.swift */,
653A256B28338800005D4D48 /* SchedulerTests.swift */,
Expand Down Expand Up @@ -519,6 +521,7 @@
);
fileSystemSynchronizedGroups = (
45E2F12D2D8291E30097C339 /* HydrationUITests */,
4D490EDC2D86A78200C7E06F /* ProteinUITests */,
);
name = Stanford360UITests;
packageProductDependencies = (
Expand Down

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

45 changes: 23 additions & 22 deletions Stanford360/Protein/Model/PromptTemplate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,25 +9,26 @@
// SPDX-License-Identifier: MIT
//

// import Foundation
//
// class ProteinPromptConstructor: ObservableObject {
// func constructPrompt(mealName: String) -> String {
// let prompt = """
// You are an expert in nutritional science with a focus on dietary needs for children aged 10-15.
// Here are some important information that you can refer:
// 1. Those belong to Meats, Poultry, and Fish usually around 6-10 grams.
// 2. Those belong to Soy and Vegetable Protein usually around 3-13 grams.
// 3. Those belong to Legumes and Nuts usually around 5-10 grams.
// 4. Those belong to Milk and Dairy usually around 10 grams.
// 5. Those belong to Vegetables usually around 2 grams.
// 6. Those belong to Grains usually around 5-10 grams.
// Task:
// 1. Analyze the meal name: "\(mealName)"
// 2. Determine the appropriate protein content (in grams) based on nutritional standards for this age group.
// 3. Respond with a single numeric value representing the protein content in grams.
// 4. Do not include any additional text in your response.
// """
// return prompt
// }
// }
import Foundation

class ProteinPromptConstructor: ObservableObject {
func constructPrompt(mealName: String) -> String {
let prompt = """
You are an expert in nutritional science with a focus on dietary needs for children aged 10-15.
Here are some important information that you can refer:
1. Those belong to Meats, Poultry, and Fish usually around 6-10 grams.
2. Those belong to Soy and Vegetable Protein usually around 3-13 grams.
3. Those belong to Legumes and Nuts usually around 5-10 grams.
4. Those belong to Milk and Dairy usually around 10 grams.
5. Those belong to Vegetables usually around 5 grams.
6. Those belong to Grains usually around 10 grams.
7. Other food contains different components usually has more protein, such as hamburger.
Task:
1. Analyze the meal name: "\(mealName)"
2. Determine the appropriate protein content (in grams) based on nutritional standards for this age group.
3. Respond with a single numeric value representing the protein content in grams.
4. Do not include any additional text in your response.
"""
return prompt
}
}
11 changes: 0 additions & 11 deletions Stanford360/Protein/Model/ProteinManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -73,17 +73,6 @@ class ProteinManager: Module, EnvironmentAccessible {
let totalIntake = getTodayTotalGrams()
return milestoneManager.getLatestMilestone(total: totalIntake)
}

// Add a new meal to the list
// func addMeal(name: String, proteinGrams: Double, imageURL: String? = nil, timestamp: Date = Date()) {
// let newMeal = Meal(name: name, proteinGrams: proteinGrams, imageURL: imageURL, timestamp: timestamp)
// meals.append(newMeal)
// }

// Delete a meal from the list by its name
// func deleteMeal(byName name: String) {
// meals.removeAll { $0.name == name }
// }

// Update an existing meal's details
// func updateMeal(
Expand Down
12 changes: 0 additions & 12 deletions Stanford360/Protein/Service/FirebaseManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,18 +17,6 @@ extension Stanford360Standard {
return
}

// var updatedMeal = meal
//
// // If an image is selected, upload the image and get its download URL
// if let image = selectedImage {
// if let imageURL = await uploadImageToFirebase(image, imageName: meal.name) {
// updatedMeal.imageURL = imageURL
// print("✅ Image URL uploaded: \(imageURL)")
// } else {
// print("❌ Failed to upload image.")
// }
// }

// store the Meal to Firestore
do {
let docRef = try await configuration.userDocumentReference
Expand Down
88 changes: 44 additions & 44 deletions Stanford360/Protein/View/AddMealView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
@Environment(\.dismiss) private var dismiss
@Environment(Stanford360Standard.self) private var standard
@Environment(ProteinManager.self) private var proteinManager
// @Environment(LLMRunner.self) var runner
@Environment(LLMRunner.self) var runner

// LLM runner state for protein
// Original state variables
Expand All @@ -42,7 +42,7 @@
@State private var classificationOptions: [String] = []
@State private var isProcessing: Bool = false
@State private var errorMessage: String?
// @StateObject private var promptTemplate = ProteinPromptConstructor()
@StateObject private var promptTemplate = ProteinPromptConstructor()
@StateObject private var classifier = ImageClassifier()

var body: some View {
Expand All @@ -60,9 +60,9 @@
.onChange(of: selectedImage) { _, newImage in
classifier.image = newImage
}
// .onChange(of: mealName) { newMealName in
// handleMealNameChange(newMealName)
// }
.onChange(of: mealName) { newMealName in

Check warning on line 63 in Stanford360/Protein/View/AddMealView.swift

View workflow job for this annotation

GitHub Actions / Build and Test / Test using xcodebuild or run fastlane

'onChange(of:perform:)' was deprecated in iOS 17.0: Use `onChange` with a two or zero parameter action closure instead.
handleMealNameChange(newMealName)
}
.onReceive(Publishers.keyboardHeight) { height in
withAnimation(.easeOut(duration: 0.25)) {
keyboardOffset = height > 0 ? height - 30 : 0 // Slight adjustment for a more natural feel
Expand Down Expand Up @@ -98,15 +98,15 @@
}
}

// func handleMealNameChange(_ newMealName: String) {
// if !newMealName.isEmpty {
// Task {
// await getMealProtein(meal: newMealName)
// }
// } else {
// proteinAmount = ""
// }
// }
func handleMealNameChange(_ newMealName: String) {
if !newMealName.isEmpty {
Task {
await getMealProtein(meal: newMealName)
}
} else {
proteinAmount = ""
}
}
}

// MARK: - Main Content
Expand Down Expand Up @@ -344,36 +344,36 @@
}
}

// extension AddMealView {
// func getMealProtein(meal: String) async {
// await MainActor.run {
// self.proteinAmount = ""
// }
// let prompt = promptTemplate.constructPrompt(mealName: meal)
//
// let llmSchema = LLMLocalSchema(
// // model: .custom(id: <#T##String#>),
// model: .llama3_2_1B_4bit,
// parameters: .init(
// systemPrompt: prompt
// )
// )
// let llmSession = runner(with: llmSchema)
// var output = ""
//
// do {
// for try await token in try await llmSession.generate() {
// output.append(token)
// }
// await MainActor.run {
// self.proteinAmount = output
// }
// print("Protein extracted is ", proteinAmount)
// } catch {
// print("Error generating protein: \(error)")
// }
// }
// }
extension AddMealView {
func getMealProtein(meal: String) async {
await MainActor.run {
self.proteinAmount = ""
}
let prompt = promptTemplate.constructPrompt(mealName: meal)

let llmSchema = LLMLocalSchema(
// model: .custom(id: <#T##String#>),
model: .llama3_2_1B_4bit,
parameters: .init(
systemPrompt: prompt
)
)
let llmSession = runner(with: llmSchema)
var output = ""

do {
for try await token in try await llmSession.generate() {
output.append(token)
}
await MainActor.run {
self.proteinAmount = output
}
print("Protein extracted is ", proteinAmount)
} catch {
print("Error generating protein: \(error)")
}
}
}


extension AddMealView {
Expand Down
38 changes: 19 additions & 19 deletions Stanford360/Protein/View/LLMLocalOnboardingDownloadView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,22 +11,22 @@
// SPDX-License-Identifier: MIT
//

// import SpeziLLM
// import SpeziLLMLocal
// import SpeziLLMLocalDownload
// import SpeziOnboarding
// import SwiftUI
//
// struct LLMLocalOnboardingDownloadView: View {
// @Environment(OnboardingNavigationPath.self) private var onboardingNavigationPath
//
//
// var body: some View {
// LLMLocalDownloadView(
// model: .llama3_2_1B_4bit,
// downloadDescription: "The Llama3 1B model will be downloaded"
// ) {
// onboardingNavigationPath.nextStep()
// }
// }
// }
import SpeziLLM
import SpeziLLMLocal
import SpeziLLMLocalDownload
import SpeziOnboarding
import SwiftUI

struct LLMLocalOnboardingDownloadView: View {

Check warning on line 20 in Stanford360/Protein/View/LLMLocalOnboardingDownloadView.swift

View workflow job for this annotation

GitHub Actions / Periphery / Periphery

Struct 'LLMLocalOnboardingDownloadView' is unused
@Environment(OnboardingNavigationPath.self) private var onboardingNavigationPath


var body: some View {
LLMLocalDownloadView(
model: .llama3_2_1B_4bit,
downloadDescription: "The Llama3 1B model will be downloaded"
) {
onboardingNavigationPath.nextStep()
}
}
}
7 changes: 4 additions & 3 deletions Stanford360/Stanford360Delegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -65,9 +65,10 @@ class Stanford360Delegate: SpeziAppDelegate {
ProteinManager()
HealthKitManager()
PatientManager()
LLMRunner {
LLMLocalPlatform()
}

LLMRunner {
LLMLocalPlatform()
}
}
}

Expand Down
51 changes: 51 additions & 0 deletions Stanford360UITests/ProteinUITests/MealDetailViewTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
// This source file is part of the Stanford 360 based on the Stanford Spezi Template Application project
//
// SPDX-FileCopyrightText: 2025 Stanford University
//
// SPDX-License-Identifier: MIT
//

import XCTest

final class MealDetailViewTests: XCTestCase {
@MainActor
override func setUp() async throws {
continueAfterFailure = false
let app = XCUIApplication()
app.launchArguments = ["--skipOnboarding"]
app.launch()

let dontAllowIdentifier = app.buttons["UIA.Health.AuthSheet.CancelButton"]
if dontAllowIdentifier.waitForExistence(timeout: 5) {
dontAllowIdentifier.tap()
}
}

// Test Meal Detail View Displays Information Correctly
@MainActor
func testMealDetailViewDisplaysInfo() throws {
let app = XCUIApplication()

XCTAssertTrue(app.wait(for: .runningForeground, timeout: 5))

XCTAssertTrue(app.tabBars["Tab Bar"].buttons["Protein"].exists)
app.tabBars["Tab Bar"].buttons["Protein"].tap()

let historyButton = app.segmentedControls.buttons["History"]
XCTAssertTrue(historyButton.waitForExistence(timeout: 2), "History tab should exist")
historyButton.tap()

let mealEntry = app.staticTexts["mealLogEntry"]
XCTAssertTrue(mealEntry.waitForExistence(timeout: 2), "Meal log entry should exist")

Check failure on line 39 in Stanford360UITests/ProteinUITests/MealDetailViewTests.swift

View workflow job for this annotation

GitHub Actions / Build and Test / Test using xcodebuild or run fastlane

testMealDetailViewDisplaysInfo, XCTAssertTrue failed - Meal log entry should exist
mealEntry.tap()

let mealName = app.staticTexts["mealName"]
XCTAssertTrue(mealName.waitForExistence(timeout: 2), "Meal name should be displayed")

let proteinContent = app.staticTexts["Protein Content"]
XCTAssertTrue(proteinContent.waitForExistence(timeout: 2), "Protein content card should be visible")

let intakeTime = app.staticTexts["Intake time"]
XCTAssertTrue(intakeTime.waitForExistence(timeout: 2), "Intake time card should be visible")
}
}
Loading
Loading