AR Version Pre-test pt2
This commit is contained in:
@@ -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;
|
||||||
|
|||||||
@@ -7,13 +7,8 @@
|
|||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
enum AppConfig {
|
|
||||||
// MARK: - Overshoot Vision API
|
|
||||||
/// 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
|
|
||||||
|
|
||||||
|
enum AppConfig: Sendable {
|
||||||
// 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
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
if showARView {
|
||||||
|
ARViewContainer(
|
||||||
|
detectedPlanes: $detectedPlanes,
|
||||||
|
lastRaycastResult: $lastRaycastResult
|
||||||
|
)
|
||||||
|
.ignoresSafeArea()
|
||||||
|
} else {
|
||||||
CameraPreviewView(previewLayer: viewModel.getPreviewLayer())
|
CameraPreviewView(previewLayer: viewModel.getPreviewLayer())
|
||||||
.ignoresSafeArea()
|
.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,9 +68,11 @@ struct ScannerView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
.task {
|
.task {
|
||||||
|
if !showARView {
|
||||||
await viewModel.setupCamera()
|
await viewModel.setupCamera()
|
||||||
viewModel.startCamera()
|
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,7 +168,30 @@ 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 {
|
||||||
|
Button {
|
||||||
|
withAnimation {
|
||||||
|
showARView.toggle()
|
||||||
|
if !showARView {
|
||||||
|
viewModel.startCamera()
|
||||||
|
} else {
|
||||||
|
viewModel.stopCamera()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} label: {
|
||||||
|
Label(showARView ? "Exit AR Mode" : "Start AR Scan", systemImage: showARView ? "camera.fill" : "arkit")
|
||||||
|
.font(.headline)
|
||||||
|
.foregroundStyle(.white)
|
||||||
|
.frame(maxWidth: .infinity)
|
||||||
|
.padding()
|
||||||
|
.background(showARView ? Color.orange : Color.blue)
|
||||||
|
.clipShape(RoundedRectangle(cornerRadius: 16))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Main scanning action button (only in non-AR mode)
|
||||||
|
if !showARView {
|
||||||
if viewModel.isScanning {
|
if viewModel.isScanning {
|
||||||
Button {
|
Button {
|
||||||
viewModel.stopScanning()
|
viewModel.stopScanning()
|
||||||
@@ -148,15 +208,16 @@ struct ScannerView: View {
|
|||||||
Button {
|
Button {
|
||||||
viewModel.startScanning()
|
viewModel.startScanning()
|
||||||
} label: {
|
} label: {
|
||||||
Label("Scan Fridge", systemImage: "camera.fill")
|
Label("Detect Ingredients", systemImage: "camera.viewfinder")
|
||||||
.font(.headline)
|
.font(.headline)
|
||||||
.foregroundStyle(.white)
|
.foregroundStyle(.white)
|
||||||
.frame(maxWidth: .infinity)
|
.frame(maxWidth: .infinity)
|
||||||
.padding()
|
.padding()
|
||||||
.background(Color.blue)
|
.background(Color.green)
|
||||||
.clipShape(RoundedRectangle(cornerRadius: 16))
|
.clipShape(RoundedRectangle(cornerRadius: 16))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Secondary actions
|
// Secondary actions
|
||||||
if !viewModel.detectedIngredients.isEmpty {
|
if !viewModel.detectedIngredients.isEmpty {
|
||||||
@@ -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))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user