AR Version Pre-test pt2

This commit is contained in:
2026-02-14 23:11:38 -06:00
parent f7f14b2c5d
commit 45035e67e0
7 changed files with 105 additions and 41 deletions

View File

@@ -361,6 +361,7 @@
SDKROOT = iphoneos; SDKROOT = iphoneos;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)";
SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 6.0;
}; };
name = Debug; name = Debug;
}; };
@@ -417,6 +418,7 @@
MTL_FAST_MATH = YES; MTL_FAST_MATH = YES;
SDKROOT = iphoneos; SDKROOT = iphoneos;
SWIFT_COMPILATION_MODE = wholemodule; SWIFT_COMPILATION_MODE = wholemodule;
SWIFT_VERSION = 6.0;
VALIDATE_PRODUCT = YES; VALIDATE_PRODUCT = YES;
}; };
name = Release; name = Release;

View File

@@ -7,13 +7,8 @@
import Foundation import Foundation
enum AppConfig {
// MARK: - Overshoot Vision API enum AppConfig: Sendable {
/// Overshoot API key for real-time video inference
/// [INSERT_OVERSHOOT_API_KEY_HERE]
static let overshootAPIKey = "INSERT_KEY_HERE"
static let overshootWebSocketURL = "wss://api.overshoot.ai/v1/stream" // Placeholder URL
// MARK: - Google Gemini API // MARK: - Google Gemini API
/// Google Gemini API key for recipe generation and reasoning /// Google Gemini API key for recipe generation and reasoning
/// [INSERT_GEMINI_API_KEY_HERE] /// [INSERT_GEMINI_API_KEY_HERE]
@@ -27,6 +22,10 @@ enum AppConfig {
/// 2. Add it to the Xcode project root /// 2. Add it to the Xcode project root
/// 3. Ensure it's added to the target /// 3. Ensure it's added to the target
// MARK: - AR Configuration
/// Enable AR-based scanning features
static let enableARScanning = true
// MARK: - Feature Flags // MARK: - Feature Flags
static let enableRealTimeDetection = true static let enableRealTimeDetection = true
static let enableCookingMode = true static let enableCookingMode = true

View File

@@ -149,11 +149,11 @@ struct ContentView: View {
Section("API Configuration") { Section("API Configuration") {
VStack(alignment: .leading, spacing: 8) { VStack(alignment: .leading, spacing: 8) {
Text("Overshoot API") Text("AR Scanning")
.font(.headline) .font(.headline)
Text(AppConfig.overshootAPIKey == "INSERT_KEY_HERE" ? "Not configured" : "Configured") Text(AppConfig.enableARScanning ? "Enabled" : "Disabled")
.font(.caption) .font(.caption)
.foregroundStyle(AppConfig.overshootAPIKey == "INSERT_KEY_HERE" ? .red : .green) .foregroundStyle(AppConfig.enableARScanning ? .green : .red)
} }
VStack(alignment: .leading, spacing: 8) { VStack(alignment: .leading, spacing: 8) {

View File

@@ -15,9 +15,11 @@ import ARKit
/// AR-based implementation for vision and spatial scanning /// AR-based implementation for vision and spatial scanning
final class ARVisionService: VisionService, @unchecked Sendable { final class ARVisionService: VisionService, @unchecked Sendable {
nonisolated init() {}
// MARK: - VisionService Protocol Implementation // MARK: - VisionService Protocol Implementation
func detectIngredients(from stream: AsyncStream<CVPixelBuffer>) async throws -> [Ingredient] { nonisolated func detectIngredients(from stream: AsyncStream<CVPixelBuffer>) async throws -> [Ingredient] {
// Mock implementation - in a real app, this would use ML models // Mock implementation - in a real app, this would use ML models
// to detect ingredients from AR camera frames // to detect ingredients from AR camera frames
var detectedIngredients: [Ingredient] = [] var detectedIngredients: [Ingredient] = []
@@ -49,11 +51,11 @@ final class ARVisionService: VisionService, @unchecked Sendable {
.sorted { $0.confidence > $1.confidence } .sorted { $0.confidence > $1.confidence }
} }
func detectIngredients(from pixelBuffer: CVPixelBuffer) async throws -> [Ingredient] { nonisolated func detectIngredients(from pixelBuffer: CVPixelBuffer) async throws -> [Ingredient] {
return try await processARFrame(pixelBuffer) return try await processARFrame(pixelBuffer)
} }
func analyzeCookingProgress(from stream: AsyncStream<CVPixelBuffer>, for step: String) async throws -> CookingProgress { nonisolated func analyzeCookingProgress(from stream: AsyncStream<CVPixelBuffer>, for step: String) async throws -> CookingProgress {
// Mock implementation for cooking progress monitoring // Mock implementation for cooking progress monitoring
return CookingProgress( return CookingProgress(
isComplete: false, isComplete: false,
@@ -64,7 +66,7 @@ final class ARVisionService: VisionService, @unchecked Sendable {
// MARK: - Private Helper Methods // MARK: - Private Helper Methods
private func processARFrame(_ pixelBuffer: CVPixelBuffer) async throws -> [Ingredient] { nonisolated private func processARFrame(_ pixelBuffer: CVPixelBuffer) async throws -> [Ingredient] {
// Mock ingredient detection // Mock ingredient detection
// In a real implementation, this would use Vision framework or ML models // In a real implementation, this would use Vision framework or ML models
// to detect objects in the AR camera feed // to detect objects in the AR camera feed

View File

@@ -44,7 +44,7 @@ final class CookingModeViewModel: ObservableObject {
} }
nonisolated init(recipe: Recipe, nonisolated init(recipe: Recipe,
visionService: VisionService = OvershootVisionService(), visionService: VisionService = ARVisionService(),
recipeService: RecipeService = GeminiRecipeService(), recipeService: RecipeService = GeminiRecipeService(),
cameraManager: CameraManager = CameraManager()) { cameraManager: CameraManager = CameraManager()) {
self.recipe = recipe self.recipe = recipe

View File

@@ -23,7 +23,7 @@ final class ScannerViewModel: ObservableObject {
private let cameraManager: CameraManager private let cameraManager: CameraManager
private var scanTask: Task<Void, Never>? private var scanTask: Task<Void, Never>?
nonisolated init(visionService: VisionService = OvershootVisionService(), nonisolated init(visionService: VisionService = ARVisionService(),
cameraManager: CameraManager = CameraManager()) { cameraManager: CameraManager = CameraManager()) {
self.visionService = visionService self.visionService = visionService
self.cameraManager = cameraManager self.cameraManager = cameraManager

View File

@@ -2,23 +2,35 @@
// ScannerView.swift // ScannerView.swift
// SousChefAI // SousChefAI
// //
// Camera view for scanning and detecting ingredients in real-time // AR camera view for scanning and detecting ingredients in real-time
// //
import SwiftUI import SwiftUI
import AVFoundation import ARKit
import RealityKit
struct ScannerView: View { struct ScannerView: View {
@StateObject private var viewModel = ScannerViewModel() @StateObject private var viewModel = ScannerViewModel()
@State private var showingInventory = false @State private var showingInventory = false
@State private var showingManualEntry = false @State private var showingManualEntry = false
@State private var detectedPlanes = 0
@State private var lastRaycastResult = ""
@State private var showARView = false
var body: some View { var body: some View {
NavigationStack { NavigationStack {
ZStack { ZStack {
// Camera preview // AR camera preview or regular camera
CameraPreviewView(previewLayer: viewModel.getPreviewLayer()) if showARView {
ARViewContainer(
detectedPlanes: $detectedPlanes,
lastRaycastResult: $lastRaycastResult
)
.ignoresSafeArea() .ignoresSafeArea()
} else {
CameraPreviewView(previewLayer: viewModel.getPreviewLayer())
.ignoresSafeArea()
}
// Overlay UI // Overlay UI
VStack { VStack {
@@ -26,6 +38,12 @@ struct ScannerView: View {
statusBar statusBar
.padding() .padding()
// AR Debug info (only when AR is active)
if showARView {
arDebugInfo
.padding(.horizontal)
}
Spacer() Spacer()
// Detected ingredients list // Detected ingredients list
@@ -38,7 +56,7 @@ struct ScannerView: View {
.padding() .padding()
} }
} }
.navigationTitle("Scan Ingredients") .navigationTitle(showARView ? "AR Scanner" : "Camera Preview")
.navigationBarTitleDisplayMode(.inline) .navigationBarTitleDisplayMode(.inline)
.toolbar { .toolbar {
ToolbarItem(placement: .navigationBarTrailing) { ToolbarItem(placement: .navigationBarTrailing) {
@@ -50,8 +68,10 @@ struct ScannerView: View {
} }
} }
.task { .task {
await viewModel.setupCamera() if !showARView {
viewModel.startCamera() await viewModel.setupCamera()
viewModel.startCamera()
}
} }
.onDisappear { .onDisappear {
viewModel.cleanup() viewModel.cleanup()
@@ -81,7 +101,7 @@ struct ScannerView: View {
private var statusBar: some View { private var statusBar: some View {
HStack { HStack {
VStack(alignment: .leading, spacing: 4) { VStack(alignment: .leading, spacing: 4) {
Text(viewModel.scanProgress) Text(showARView ? "AR Mode Active" : viewModel.scanProgress)
.font(.headline) .font(.headline)
.foregroundStyle(.white) .foregroundStyle(.white)
@@ -107,6 +127,23 @@ struct ScannerView: View {
.clipShape(RoundedRectangle(cornerRadius: 12)) .clipShape(RoundedRectangle(cornerRadius: 12))
} }
private var arDebugInfo: some View {
VStack(alignment: .leading, spacing: 4) {
Text("Detected Planes: \(detectedPlanes)")
.font(.caption)
.foregroundStyle(.white)
if !lastRaycastResult.isEmpty {
Text(lastRaycastResult)
.font(.caption2)
.foregroundStyle(.white)
}
}
.padding(8)
.background(.ultraThinMaterial)
.clipShape(RoundedRectangle(cornerRadius: 8))
}
private var detectedIngredientsOverlay: some View { private var detectedIngredientsOverlay: some View {
ScrollView(.horizontal, showsIndicators: false) { ScrollView(.horizontal, showsIndicators: false) {
HStack(spacing: 12) { HStack(spacing: 12) {
@@ -131,30 +168,54 @@ struct ScannerView: View {
private var controlsBar: some View { private var controlsBar: some View {
VStack(spacing: 16) { VStack(spacing: 16) {
// Main action button // AR Toggle button
if viewModel.isScanning { if !viewModel.isScanning {
Button { Button {
viewModel.stopScanning() withAnimation {
showARView.toggle()
if !showARView {
viewModel.startCamera()
} else {
viewModel.stopCamera()
}
}
} label: { } label: {
Label("Stop Scanning", systemImage: "stop.circle.fill") Label(showARView ? "Exit AR Mode" : "Start AR Scan", systemImage: showARView ? "camera.fill" : "arkit")
.font(.headline) .font(.headline)
.foregroundStyle(.white) .foregroundStyle(.white)
.frame(maxWidth: .infinity) .frame(maxWidth: .infinity)
.padding() .padding()
.background(Color.red) .background(showARView ? Color.orange : Color.blue)
.clipShape(RoundedRectangle(cornerRadius: 16)) .clipShape(RoundedRectangle(cornerRadius: 16))
} }
} else { }
Button {
viewModel.startScanning() // Main scanning action button (only in non-AR mode)
} label: { if !showARView {
Label("Scan Fridge", systemImage: "camera.fill") if viewModel.isScanning {
.font(.headline) Button {
.foregroundStyle(.white) viewModel.stopScanning()
.frame(maxWidth: .infinity) } label: {
.padding() Label("Stop Scanning", systemImage: "stop.circle.fill")
.background(Color.blue) .font(.headline)
.clipShape(RoundedRectangle(cornerRadius: 16)) .foregroundStyle(.white)
.frame(maxWidth: .infinity)
.padding()
.background(Color.red)
.clipShape(RoundedRectangle(cornerRadius: 16))
}
} else {
Button {
viewModel.startScanning()
} label: {
Label("Detect Ingredients", systemImage: "camera.viewfinder")
.font(.headline)
.foregroundStyle(.white)
.frame(maxWidth: .infinity)
.padding()
.background(Color.green)
.clipShape(RoundedRectangle(cornerRadius: 16))
}
} }
} }
@@ -168,7 +229,7 @@ struct ScannerView: View {
.foregroundStyle(.white) .foregroundStyle(.white)
.frame(maxWidth: .infinity) .frame(maxWidth: .infinity)
.padding() .padding()
.background(Color.green) .background(Color.purple)
.clipShape(RoundedRectangle(cornerRadius: 16)) .clipShape(RoundedRectangle(cornerRadius: 16))
} }
} }