vibefast-cli 1.3.0 → 1.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +30 -95
- package/dist/__tests__/recipes.test.js +8 -9
- package/dist/__tests__/recipes.test.js.map +1 -1
- package/dist/commands/add.d.ts.map +1 -1
- package/dist/commands/add.js +271 -125
- package/dist/commands/add.js.map +1 -1
- package/dist/commands/checklist.d.ts.map +1 -1
- package/dist/commands/checklist.js +85 -44
- package/dist/commands/checklist.js.map +1 -1
- package/dist/commands/health.d.ts.map +1 -1
- package/dist/commands/health.js +13 -4
- package/dist/commands/health.js.map +1 -1
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +118 -26
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/migrate.d.ts +3 -0
- package/dist/commands/migrate.d.ts.map +1 -0
- package/dist/commands/migrate.js +202 -0
- package/dist/commands/migrate.js.map +1 -0
- package/dist/commands/remove.d.ts.map +1 -1
- package/dist/commands/remove.js +61 -3
- package/dist/commands/remove.js.map +1 -1
- package/dist/core/auth.d.ts.map +1 -1
- package/dist/core/auth.js +20 -18
- package/dist/core/auth.js.map +1 -1
- package/dist/core/codemod.d.ts +33 -0
- package/dist/core/codemod.d.ts.map +1 -1
- package/dist/core/codemod.js +116 -0
- package/dist/core/codemod.js.map +1 -1
- package/dist/core/detect.d.ts.map +1 -1
- package/dist/core/detect.js +24 -7
- package/dist/core/detect.js.map +1 -1
- package/dist/core/journal.d.ts +1 -0
- package/dist/core/journal.d.ts.map +1 -1
- package/dist/core/journal.js.map +1 -1
- package/dist/core/recipes.d.ts.map +1 -1
- package/dist/core/recipes.js +25 -7
- package/dist/core/recipes.js.map +1 -1
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/docs/architecture.md +50 -0
- package/docs/commands.md +78 -0
- package/docs/contributing.md +27 -0
- package/docs/quickstart.md +50 -0
- package/docs/recipes.md +57 -0
- package/docs/troubleshooting.md +31 -0
- package/package.json +2 -2
- package/recipes/0/apps/native/src/components/advanced-ui/timeline/demo.tsx +445 -0
- package/recipes/0/apps/native/src/components/advanced-ui/timeline/timeline-view.tsx +355 -0
- package/recipes/0/apps/native/src/components/advanced-ui/timeline/types.ts +31 -0
- package/recipes/0/recipe.json +18 -0
- package/recipes/animated-chip/apps/native/src/components/advanced-ui/chip/demo.tsx +2 -1
- package/recipes/animated-chip/recipe.json +5 -2
- package/recipes/animated-chip-native@latest.zip +0 -0
- package/recipes/animated-chip@latest.zip +0 -0
- package/recipes/animated-switch/apps/native/src/components/advanced-ui/switch/demo.tsx +1 -1
- package/recipes/animated-switch/recipe.json +5 -2
- package/recipes/animated-switch-native@latest.zip +0 -0
- package/recipes/animated-switch@latest.zip +0 -0
- package/recipes/audio-recorder/apps/native/src/features/audio-recorder/components/audio-recorder.tsx +2 -1
- package/recipes/audio-recorder/apps/native/src/features/audio-recorder/demo/with-recording-list-demo.tsx +2 -2
- package/recipes/audio-recorder/recipe.json +7 -2
- package/recipes/audio-recorder-native@latest.zip +0 -0
- package/recipes/audio-recorder-supabase/apps/native/src/features/audio-recorder/components/audio-recorder.tsx +2 -1
- package/recipes/audio-recorder-supabase/apps/native/src/features/audio-recorder/demo/with-recording-list-demo.tsx +2 -1
- package/recipes/audio-recorder-supabase/recipe.json +12 -33
- package/recipes/audio-recorder-supabase-native@latest.zip +0 -0
- package/recipes/audio-recorder-supabase@latest.zip +0 -0
- package/recipes/audio-recorder@latest.zip +0 -0
- package/recipes/charts/apps/native/src/app/charts/index.tsx +3 -0
- package/recipes/charts/apps/native/src/features/charts/components/bar-chart.tsx +3 -1
- package/recipes/charts/apps/native/src/features/charts/components/candlestick-chart.tsx +3 -1
- package/recipes/charts/apps/native/src/features/charts/components/column-chart.tsx +3 -1
- package/recipes/charts/apps/native/src/features/charts/components/doughnut-chart.tsx +3 -1
- package/recipes/charts/apps/native/src/features/charts/components/line-chart.tsx +3 -1
- package/recipes/charts/apps/native/src/features/charts/components/radar-chart.tsx +3 -1
- package/recipes/charts/apps/native/src/features/charts/components/stacked-bar-chart.tsx +3 -1
- package/recipes/charts/recipe.json +13 -4
- package/recipes/charts-native@latest.zip +0 -0
- package/recipes/charts@latest.zip +0 -0
- package/recipes/chatbot/apps/native/src/features/chatbot/components/chat-markdown.tsx +86 -86
- package/recipes/chatbot/apps/native/src/features/chatbot/components/markdown/code-block.tsx +4 -4
- package/recipes/chatbot/recipe.json +3 -40
- package/recipes/chatbot-native@latest.zip +0 -0
- package/recipes/chatbot-supabase/apps/native/src/features/chatbot/components/chat-markdown.tsx +4 -1
- package/recipes/chatbot-supabase/apps/native/src/features/chatbot/components/markdown/code-block.tsx +86 -53
- package/recipes/chatbot-supabase/recipe.json +3 -69
- package/recipes/chatbot-supabase-native@latest.zip +0 -0
- package/recipes/chatbot-supabase@latest.zip +0 -0
- package/recipes/chatbot@latest.zip +0 -0
- package/recipes/glowing-button/recipe.json +6 -2
- package/recipes/glowing-button-native@latest.zip +0 -0
- package/recipes/glowing-button@latest.zip +0 -0
- package/recipes/image-analysis/apps/native/src/app/analysis/[type]/_layout.tsx +5 -0
- package/recipes/image-analysis/apps/native/src/app/analysis/[type]/analysis-options.tsx +50 -0
- package/recipes/image-analysis/apps/native/src/app/analysis/[type]/camera.tsx +2 -0
- package/recipes/image-analysis/apps/native/src/app/analysis/[type]/index.tsx +50 -0
- package/recipes/image-analysis/apps/native/src/app/analysis/[type]/loading.tsx +50 -0
- package/recipes/image-analysis/apps/native/src/app/analysis/[type]/results.tsx +2 -0
- package/recipes/image-analysis/apps/native/src/app/analysis/[type]/trait-details.tsx +3 -0
- package/recipes/image-analysis/apps/native/src/features/image-analyzer/app/analysis-options-screen.tsx +2 -2
- package/recipes/image-analysis/apps/native/src/features/image-analyzer/app/camera.tsx +72 -65
- package/recipes/image-analysis/apps/native/src/features/image-analyzer/app/image-capture-screen.tsx +65 -47
- package/recipes/image-analysis/apps/native/src/features/image-analyzer/app/loading-screen.tsx +43 -2
- package/recipes/image-analysis/apps/native/src/features/image-analyzer/app/loading.tsx +34 -1
- package/recipes/image-analysis/apps/native/src/features/image-analyzer/hooks/use-image-analysis.ts +83 -2
- package/recipes/image-analysis/recipe.json +11 -19
- package/recipes/image-analysis-native@latest.zip +0 -0
- package/recipes/image-analysis-supabase/apps/native/src/app/analysis/[type]/_layout.tsx +5 -0
- package/recipes/image-analysis-supabase/apps/native/src/app/analysis/[type]/analysis-options.tsx +50 -0
- package/recipes/image-analysis-supabase/apps/native/src/app/analysis/[type]/camera.tsx +2 -0
- package/recipes/image-analysis-supabase/apps/native/src/app/analysis/[type]/index.tsx +50 -0
- package/recipes/image-analysis-supabase/apps/native/src/app/analysis/[type]/loading.tsx +50 -0
- package/recipes/image-analysis-supabase/apps/native/src/app/analysis/[type]/results.tsx +2 -0
- package/recipes/image-analysis-supabase/apps/native/src/app/analysis/[type]/trait-details.tsx +3 -0
- package/recipes/image-analysis-supabase/recipe.json +10 -70
- package/recipes/image-analysis-supabase-native@latest.zip +0 -0
- package/recipes/image-analysis-supabase@latest.zip +0 -0
- package/recipes/image-analysis@latest.zip +0 -0
- package/recipes/image-analyzer/apps/native/src/app/(root)/(protected)/image-analyzer/index.tsx +2 -0
- package/recipes/image-generator/apps/native/src/app/image-generator/gallery.tsx +3 -0
- package/recipes/image-generator/apps/native/src/app/image-generator/index.tsx +3 -0
- package/recipes/image-generator/recipe.json +8 -18
- package/recipes/image-generator-native@latest.zip +0 -0
- package/recipes/image-generator-supabase/recipe.json +6 -62
- package/recipes/image-generator-supabase-native@latest.zip +0 -0
- package/recipes/image-generator-supabase@latest.zip +0 -0
- package/recipes/image-generator@latest.zip +0 -0
- package/recipes/ios-widget/recipe.json +18 -119
- package/recipes/ios-widget-native@latest.zip +0 -0
- package/recipes/ios-widget@latest.zip +0 -0
- package/recipes/number-stepper/apps/native/src/components/advanced-ui/stepper/demo.tsx +1 -1
- package/recipes/number-stepper/recipe.json +5 -2
- package/recipes/number-stepper-native@latest.zip +0 -0
- package/recipes/number-stepper@latest.zip +0 -0
- package/recipes/onboarding/apps/native/src/features/onboarding/expense-tracker/components/interactive-onboarding.tsx +11 -18
- package/recipes/onboarding/apps/native/src/features/onboarding/expense-tracker/components/steps/ai-tone-step.tsx +5 -7
- package/recipes/onboarding/apps/native/src/features/onboarding/expense-tracker/components/steps/currency-step.tsx +9 -7
- package/recipes/onboarding/apps/native/src/features/onboarding/expense-tracker/components/steps/feature-ai-step.tsx +8 -7
- package/recipes/onboarding/apps/native/src/features/onboarding/expense-tracker/components/steps/feature-chatbot-step.tsx +6 -5
- package/recipes/onboarding/apps/native/src/features/onboarding/expense-tracker/components/steps/feature-manual-step.tsx +4 -3
- package/recipes/onboarding/apps/native/src/features/onboarding/expense-tracker/components/steps/feature-scan-step.tsx +6 -5
- package/recipes/onboarding/apps/native/src/features/onboarding/expense-tracker/components/steps/main-reason-step.tsx +5 -7
- package/recipes/onboarding/apps/native/src/features/onboarding/expense-tracker/components/steps/notification-step.tsx +7 -6
- package/recipes/onboarding/apps/native/src/features/onboarding/expense-tracker/components/steps/overspend-step.tsx +5 -7
- package/recipes/onboarding/apps/native/src/features/onboarding/expense-tracker/components/steps/personalizing-step.tsx +8 -7
- package/recipes/onboarding/apps/native/src/features/onboarding/expense-tracker/components/steps/rating-step.tsx +6 -5
- package/recipes/onboarding/apps/native/src/features/onboarding/expense-tracker/components/steps/reminder-step.tsx +5 -6
- package/recipes/onboarding/apps/native/src/features/onboarding/expense-tracker/components/steps/safety-step.tsx +5 -4
- package/recipes/onboarding/apps/native/src/features/onboarding/expense-tracker/components/steps/struggle-step.tsx +5 -7
- package/recipes/onboarding/apps/native/src/features/onboarding/expense-tracker/components/steps/welcome-step.tsx +7 -6
- package/recipes/onboarding/apps/native/src/features/onboarding/expense-tracker/components/ui/onboarding-header.tsx +4 -3
- package/recipes/onboarding/recipe.json +9 -6
- package/recipes/onboarding-native@latest.zip +0 -0
- package/recipes/onboarding@latest.zip +0 -0
- package/recipes/payments/apps/native/src/app/paywall/index.tsx +74 -0
- package/recipes/payments/apps/native/src/app/paywall/local.tsx +25 -0
- package/recipes/payments/apps/native/src/app/paywall/remote.tsx +23 -0
- package/recipes/payments/packages/backend/convex/payments.ts +21 -3
- package/recipes/payments/recipe.json +14 -34
- package/recipes/payments-native@latest.zip +0 -0
- package/recipes/payments-supabase/apps/native/src/app/paywall/index.tsx +74 -0
- package/recipes/payments-supabase/apps/native/src/app/paywall/local.tsx +25 -0
- package/recipes/payments-supabase/apps/native/src/app/paywall/remote.tsx +23 -0
- package/recipes/payments-supabase/recipe.json +16 -44
- package/recipes/payments-supabase-native@latest.zip +0 -0
- package/recipes/payments-supabase@latest.zip +0 -0
- package/recipes/payments@latest.zip +0 -0
- package/recipes/posthog/apps/native/src/components/analytics/navigation-tracker.tsx +14 -0
- package/recipes/posthog/apps/native/src/lib/hooks/use-navigation-analytics.ts +44 -0
- package/recipes/posthog/apps/native/src/providers/posthog-provider.tsx +51 -0
- package/recipes/posthog/recipe.json +60 -0
- package/recipes/posthog-native@latest.zip +0 -0
- package/recipes/progress-circle/apps/native/src/components/advanced-ui/progress-bars/progress-circle-page.tsx +1 -1
- package/recipes/progress-circle/recipe.json +5 -2
- package/recipes/progress-circle-native@latest.zip +0 -0
- package/recipes/progress-circle@latest.zip +0 -0
- package/recipes/quiz/apps/native/src/app/quiz/index.tsx +47 -0
- package/recipes/quiz/recipe.json +9 -6
- package/recipes/quiz-native@latest.zip +0 -0
- package/recipes/quiz@latest.zip +0 -0
- package/recipes/screen-kits/apps/native/src/app/screen-kits/_layout.tsx +12 -0
- package/recipes/screen-kits/apps/native/src/app/screen-kits/index.tsx +114 -0
- package/recipes/screen-kits/apps/native/src/features/screen-kits/index.ts +1 -0
- package/recipes/screen-kits/apps/native/src/features/screen-kits/types.ts +28 -0
- package/recipes/screen-kits/recipe.json +26 -0
- package/recipes/screen-kits-duolingo/apps/native/src/app/screen-kits/duolingo/_layout.tsx +12 -0
- package/recipes/screen-kits-duolingo/apps/native/src/app/screen-kits/duolingo/home.tsx +5 -0
- package/recipes/screen-kits-duolingo/apps/native/src/app/screen-kits/duolingo/index.tsx +5 -0
- package/recipes/screen-kits-duolingo/apps/native/src/app/screen-kits/duolingo/lesson-complete.tsx +5 -0
- package/recipes/screen-kits-duolingo/apps/native/src/app/screen-kits/duolingo/lesson-fail.tsx +5 -0
- package/recipes/screen-kits-duolingo/apps/native/src/app/screen-kits/duolingo/lesson.tsx +5 -0
- package/recipes/screen-kits-duolingo/apps/native/src/app/screen-kits/duolingo/skill-tree.tsx +5 -0
- package/recipes/screen-kits-duolingo/apps/native/src/features/screen-kits/duolingo/components/duo-button.tsx +174 -0
- package/recipes/screen-kits-duolingo/apps/native/src/features/screen-kits/duolingo/components/skill-button.tsx +186 -0
- package/recipes/screen-kits-duolingo/apps/native/src/features/screen-kits/duolingo/components/xp-header.tsx +115 -0
- package/recipes/screen-kits-duolingo/apps/native/src/features/screen-kits/duolingo/constants.ts +89 -0
- package/recipes/screen-kits-duolingo/apps/native/src/features/screen-kits/duolingo/index.ts +3 -0
- package/recipes/screen-kits-duolingo/apps/native/src/features/screen-kits/duolingo/screens/home-screen.tsx +225 -0
- package/recipes/screen-kits-duolingo/apps/native/src/features/screen-kits/duolingo/screens/lesson-complete-screen.tsx +485 -0
- package/recipes/screen-kits-duolingo/apps/native/src/features/screen-kits/duolingo/screens/lesson-fail-screen.tsx +105 -0
- package/recipes/screen-kits-duolingo/apps/native/src/features/screen-kits/duolingo/screens/lesson-screen.tsx +384 -0
- package/recipes/screen-kits-duolingo/recipe.json +58 -0
- package/recipes/screen-kits-duolingo-native@latest.zip +0 -0
- package/recipes/screen-kits-finance/apps/native/src/app/screen-kits/finance/_layout.tsx +45 -0
- package/recipes/screen-kits-finance/apps/native/src/app/screen-kits/finance/asset-detail.tsx +3 -0
- package/recipes/screen-kits-finance/apps/native/src/app/screen-kits/finance/notifications.tsx +3 -0
- package/recipes/screen-kits-finance/apps/native/src/app/screen-kits/finance/receive.tsx +3 -0
- package/recipes/screen-kits-finance/apps/native/src/app/screen-kits/finance/send.tsx +3 -0
- package/recipes/screen-kits-finance/apps/native/src/app/screen-kits/finance/swap.tsx +3 -0
- package/recipes/screen-kits-finance/apps/native/src/app/screen-kits/finance/wallet.tsx +3 -0
- package/recipes/screen-kits-finance/apps/native/src/features/screen-kits/finance/components/ActionButtons.tsx +78 -0
- package/recipes/screen-kits-finance/apps/native/src/features/screen-kits/finance/components/AssetRow.tsx +94 -0
- package/recipes/screen-kits-finance/apps/native/src/features/screen-kits/finance/components/BalanceCard.tsx +118 -0
- package/recipes/screen-kits-finance/apps/native/src/features/screen-kits/finance/constants.ts +85 -0
- package/recipes/screen-kits-finance/apps/native/src/features/screen-kits/finance/screens/asset-detail.tsx +378 -0
- package/recipes/screen-kits-finance/apps/native/src/features/screen-kits/finance/screens/notifications.tsx +210 -0
- package/recipes/screen-kits-finance/apps/native/src/features/screen-kits/finance/screens/receive-modal.tsx +317 -0
- package/recipes/screen-kits-finance/apps/native/src/features/screen-kits/finance/screens/send-modal.tsx +420 -0
- package/recipes/screen-kits-finance/apps/native/src/features/screen-kits/finance/screens/swap-modal.tsx +363 -0
- package/recipes/screen-kits-finance/apps/native/src/features/screen-kits/finance/screens/wallet-dashboard.tsx +281 -0
- package/recipes/screen-kits-finance/recipe.json +46 -0
- package/recipes/screen-kits-finance-native@latest.zip +0 -0
- package/recipes/screen-kits-fitness/apps/native/assets/sounds/timer-beep.wav +0 -0
- package/recipes/screen-kits-fitness/apps/native/src/app/screen-kits/fitness/_layout.tsx +10 -0
- package/recipes/screen-kits-fitness/apps/native/src/app/screen-kits/fitness/index.tsx +6 -0
- package/recipes/screen-kits-fitness/apps/native/src/app/screen-kits/fitness/timer.tsx +3 -0
- package/recipes/screen-kits-fitness/apps/native/src/app/screen-kits/fitness/workout.tsx +3 -0
- package/recipes/screen-kits-fitness/apps/native/src/features/screen-kits/fitness/components/timer-components.tsx +500 -0
- package/recipes/screen-kits-fitness/apps/native/src/features/screen-kits/fitness/components/timer-settings-modal.tsx +352 -0
- package/recipes/screen-kits-fitness/apps/native/src/features/screen-kits/fitness/components/workout-card.tsx +105 -0
- package/recipes/screen-kits-fitness/apps/native/src/features/screen-kits/fitness/constants.ts +189 -0
- package/recipes/screen-kits-fitness/apps/native/src/features/screen-kits/fitness/hooks/use-timer.ts +307 -0
- package/recipes/screen-kits-fitness/apps/native/src/features/screen-kits/fitness/index.ts +1 -0
- package/recipes/screen-kits-fitness/apps/native/src/features/screen-kits/fitness/screens/timer-screen.tsx +278 -0
- package/recipes/screen-kits-fitness/apps/native/src/features/screen-kits/fitness/screens/workout-dashboard.tsx +350 -0
- package/recipes/screen-kits-fitness/recipe.json +63 -0
- package/recipes/screen-kits-fitness-native@latest.zip +0 -0
- package/recipes/screen-kits-habits/apps/native/src/app/screen-kits/productivity/habits.tsx +1 -0
- package/recipes/screen-kits-habits/apps/native/src/app/screen-kits/productivity/kanban.tsx +1 -0
- package/recipes/screen-kits-habits/apps/native/src/app/screen-kits/productivity/routes.ts +4 -0
- package/recipes/screen-kits-habits/apps/native/src/features/screen-kits/productivity/components/AddTaskModal.tsx +246 -0
- package/recipes/screen-kits-habits/apps/native/src/features/screen-kits/productivity/components/DraggableTaskCard.tsx +92 -0
- package/recipes/screen-kits-habits/apps/native/src/features/screen-kits/productivity/components/KanbanColumn.tsx +238 -0
- package/recipes/screen-kits-habits/apps/native/src/features/screen-kits/productivity/components/TaskCard.tsx +144 -0
- package/recipes/screen-kits-habits/apps/native/src/features/screen-kits/productivity/components/add-habit-modal.tsx +271 -0
- package/recipes/screen-kits-habits/apps/native/src/features/screen-kits/productivity/constants.ts +295 -0
- package/recipes/screen-kits-habits/apps/native/src/features/screen-kits/productivity/kanban-utils.ts +62 -0
- package/recipes/screen-kits-habits/apps/native/src/features/screen-kits/productivity/screens/habit-tracker.tsx +1160 -0
- package/recipes/screen-kits-habits/apps/native/src/features/screen-kits/productivity/screens/kanban-board.tsx +432 -0
- package/recipes/screen-kits-habits/recipe.json +52 -0
- package/recipes/screen-kits-habits-native@latest.zip +0 -0
- package/recipes/screen-kits-native@latest.zip +0 -0
- package/recipes/sentry/apps/native/src/providers/sentry-provider.tsx +64 -0
- package/recipes/sentry/recipe.json +39 -0
- package/recipes/sentry-native@latest.zip +0 -0
- package/recipes/swipe-slider/apps/native/src/components/advanced-ui/sliders/swipe-slider-page.tsx +1 -1
- package/recipes/swipe-slider/recipe.json +5 -2
- package/recipes/swipe-slider-native@latest.zip +0 -0
- package/recipes/swipe-slider@latest.zip +0 -0
- package/recipes/timeline/apps/native/src/components/advanced-ui/timeline/demo.tsx +2 -1
- package/recipes/timeline/recipe.json +5 -2
- package/recipes/timeline-native@latest.zip +0 -0
- package/recipes/timeline@latest.zip +0 -0
- package/recipes/tracker-app/apps/native/src/app/tracker-app/index.tsx +1 -0
- package/recipes/tracker-app/recipe.json +10 -7
- package/recipes/tracker-app-native@latest.zip +0 -0
- package/recipes/tracker-app@latest.zip +0 -0
- package/recipes/upload-all.sh +8 -31
- package/recipes/voice-bot/apps/native/src/app/voice-bot/index.tsx +56 -0
- package/recipes/voice-bot/recipe.json +31 -7
- package/recipes/voice-bot-native@latest.zip +0 -0
- package/recipes/voice-bot@latest.zip +0 -0
- package/recipes/wake-word/apps/native/src/app/{(root)/(protected)/test-wake-word.tsx → test-wake-word.tsx} +43 -4
- package/recipes/wake-word/recipe.json +16 -26
- package/recipes/wake-word-native@latest.zip +0 -0
- package/recipes/wake-word@latest.zip +0 -0
- package/scripts/create-advanced-ui-recipes.sh +46 -19
- package/scripts/create-recipes.mjs +471 -117
- package/scripts/package-recipes.mjs +76 -0
- package/scripts/publish-all.sh +6 -2
- package/CHANGELOG.md +0 -198
- package/docs/archive/AUTO-DETECT-DEPS.md +0 -607
- package/docs/archive/FINAL-PACKAGE-STRATEGY.md +0 -583
- package/docs/archive/FINAL-SIMPLE-PLAN.md +0 -487
- package/docs/archive/FINAL-STATUS.md +0 -144
- package/docs/archive/FLOW-DIAGRAM.md +0 -1629
- package/docs/archive/GOTCHAS-AND-RISKS.md +0 -801
- package/docs/archive/IMPLEMENTATION-PLAN.md +0 -1360
- package/docs/archive/PLAN.md +0 -453
- package/docs/archive/PRODUCTION-READINESS.md +0 -684
- package/docs/archive/PRODUCTION-TEST-RESULTS.md +0 -465
- package/docs/archive/SIMPLIFIED-PLAN.md +0 -578
- package/docs/archive/STATUS.md +0 -199
- package/docs/archive/SUCCESS.md +0 -259
- package/docs/archive/TEST-SUMMARY.md +0 -261
- package/docs/archive/TESTING-CHECKLIST.md +0 -450
- package/docs/archive/USER-MODIFICATIONS.md +0 -448
- package/docs/decisions.md +0 -55
- package/docs/manual-testing.md +0 -91
- package/docs/next-steps.md +0 -12
- package/recipes/README.md +0 -156
- package/recipes/audio-recorder-supabase/packages/backend/src/services/recordings.ts +0 -369
- package/recipes/chatbot/apps/native/src/api-client/chatbot.ts +0 -83
- package/recipes/chatbot/packages/backend/convex/agents.ts +0 -115
- package/recipes/chatbot/packages/backend/convex/tools/index.ts +0 -18
- package/recipes/chatbot/packages/backend/convex/tools/knowledgeRetrieval.ts +0 -97
- package/recipes/chatbot/packages/backend/convex/tools/tavilySearch.ts +0 -83
- package/recipes/chatbot/packages/backend/convex/tools/userProfile.ts +0 -72
- package/recipes/chatbot-supabase/apps/native/src/api-client/supabase/chatbot.ts +0 -515
- package/recipes/chatbot-supabase/packages/backend/src/services/conversations.ts +0 -243
- package/recipes/chatbot-supabase/packages/backend/src/services/messages.ts +0 -327
- package/recipes/image-analysis/apps/native/src/api-client/image-analyzer.ts +0 -62
- package/recipes/image-analysis-supabase/packages/backend/src/services/image-analyses.ts +0 -132
- package/recipes/image-generator/apps/native/src/api-client/image-generator.ts +0 -34
- package/recipes/payments/apps/native/src/api-client/payments.ts +0 -44
- package/recipes/payments-supabase/packages/backend/src/services/payments.ts +0 -201
- package/recipes/posthog.json +0 -47
- package/recipes/revenuecat.json +0 -43
- package/recipes/sentry.json +0 -47
- package/recipes/wake-word/apps/native/assets/vosk-model/README.md +0 -103
- package/recipes/wake-word/apps/native/scripts/download-vosk-model.mjs +0 -127
- /package/recipes/{audio-recorder/apps/native/src/app/(root)/(protected) → audio-recorder-supabase/apps/native/src/app}/audio-recorder/index.tsx +0 -0
- /package/recipes/ios-widget/{targets → apps/native/targets}/widget/AppIntent.swift +0 -0
- /package/recipes/ios-widget/{targets → apps/native/targets}/widget/Assets.xcassets/AppIcon.appiconset/App-Icon-20x20@1x.png +0 -0
- /package/recipes/ios-widget/{targets → apps/native/targets}/widget/Assets.xcassets/AppIcon.appiconset/App-Icon-20x20@2x.png +0 -0
- /package/recipes/ios-widget/{targets → apps/native/targets}/widget/Assets.xcassets/AppIcon.appiconset/App-Icon-20x20@3x.png +0 -0
- /package/recipes/ios-widget/{targets → apps/native/targets}/widget/Assets.xcassets/AppIcon.appiconset/App-Icon-29x29@1x.png +0 -0
- /package/recipes/ios-widget/{targets → apps/native/targets}/widget/Assets.xcassets/AppIcon.appiconset/App-Icon-29x29@2x.png +0 -0
- /package/recipes/ios-widget/{targets → apps/native/targets}/widget/Assets.xcassets/AppIcon.appiconset/App-Icon-29x29@3x.png +0 -0
- /package/recipes/ios-widget/{targets → apps/native/targets}/widget/Assets.xcassets/AppIcon.appiconset/App-Icon-40x40@1x.png +0 -0
- /package/recipes/ios-widget/{targets → apps/native/targets}/widget/Assets.xcassets/AppIcon.appiconset/App-Icon-40x40@2x.png +0 -0
- /package/recipes/ios-widget/{targets → apps/native/targets}/widget/Assets.xcassets/AppIcon.appiconset/App-Icon-40x40@3x.png +0 -0
- /package/recipes/ios-widget/{targets → apps/native/targets}/widget/Assets.xcassets/AppIcon.appiconset/App-Icon-60x60@2x.png +0 -0
- /package/recipes/ios-widget/{targets → apps/native/targets}/widget/Assets.xcassets/AppIcon.appiconset/App-Icon-60x60@3x.png +0 -0
- /package/recipes/ios-widget/{targets → apps/native/targets}/widget/Assets.xcassets/AppIcon.appiconset/App-Icon-76x76@1x.png +0 -0
- /package/recipes/ios-widget/{targets → apps/native/targets}/widget/Assets.xcassets/AppIcon.appiconset/App-Icon-76x76@2x.png +0 -0
- /package/recipes/ios-widget/{targets → apps/native/targets}/widget/Assets.xcassets/AppIcon.appiconset/App-Icon-83.5x83.5@2x.png +0 -0
- /package/recipes/ios-widget/{targets → apps/native/targets}/widget/Assets.xcassets/AppIcon.appiconset/Contents.json +0 -0
- /package/recipes/ios-widget/{targets → apps/native/targets}/widget/Assets.xcassets/AppIcon.appiconset/ItunesArtwork@2x.png +0 -0
- /package/recipes/ios-widget/{targets → apps/native/targets}/widget/CalorieTrackerWidget.swift +0 -0
- /package/recipes/ios-widget/{targets → apps/native/targets}/widget/HabitTrackerWidget.swift +0 -0
- /package/recipes/ios-widget/{targets → apps/native/targets}/widget/Info.plist +0 -0
- /package/recipes/ios-widget/{targets → apps/native/targets}/widget/WidgetLiveActivity.swift +0 -0
- /package/recipes/ios-widget/{targets → apps/native/targets}/widget/expo-target.config.js +0 -0
- /package/recipes/ios-widget/{targets → apps/native/targets}/widget/generated.entitlements +0 -0
- /package/recipes/ios-widget/{targets → apps/native/targets}/widget/index.swift +0 -0
- /package/recipes/ios-widget/{targets → apps/native/targets}/widget/widgets.swift +0 -0
|
@@ -2,7 +2,13 @@ import { MaterialIcons } from '@expo/vector-icons';
|
|
|
2
2
|
import { type CameraType, CameraView, useCameraPermissions } from 'expo-camera';
|
|
3
3
|
import { Stack, useLocalSearchParams, useRouter } from 'expo-router';
|
|
4
4
|
import React, { useCallback, useRef, useState } from 'react';
|
|
5
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
ActivityIndicator,
|
|
7
|
+
Alert,
|
|
8
|
+
StyleSheet,
|
|
9
|
+
TouchableOpacity,
|
|
10
|
+
View,
|
|
11
|
+
} from 'react-native';
|
|
6
12
|
|
|
7
13
|
import { Button, Image, Text } from '@/components/ui';
|
|
8
14
|
import { useCameraCaptureStore } from '@/features/image-analyzer/hooks/use-image-analysis';
|
|
@@ -11,7 +17,6 @@ type CameraScreenParams = {
|
|
|
11
17
|
stepId: string;
|
|
12
18
|
stepLabel: string;
|
|
13
19
|
stepDescription: string;
|
|
14
|
-
guideText: string;
|
|
15
20
|
};
|
|
16
21
|
|
|
17
22
|
/**
|
|
@@ -28,11 +33,12 @@ export default function CameraScreen() {
|
|
|
28
33
|
const [facing, setFacing] = useState<CameraType>('back');
|
|
29
34
|
const [capturedImageUri, setCapturedImageUri] = useState<string | null>(null);
|
|
30
35
|
const [isCapturing, setIsCapturing] = useState(false);
|
|
36
|
+
const [isCameraReady, setIsCameraReady] = useState(false);
|
|
31
37
|
const cameraRef = useRef<CameraView>(null);
|
|
32
38
|
|
|
33
39
|
// Handle photo capture
|
|
34
40
|
const handleTakePhoto = useCallback(async () => {
|
|
35
|
-
if (!cameraRef.current || isCapturing) return;
|
|
41
|
+
if (!cameraRef.current || isCapturing || !isCameraReady) return;
|
|
36
42
|
|
|
37
43
|
try {
|
|
38
44
|
setIsCapturing(true);
|
|
@@ -49,7 +55,7 @@ export default function CameraScreen() {
|
|
|
49
55
|
} finally {
|
|
50
56
|
setIsCapturing(false);
|
|
51
57
|
}
|
|
52
|
-
}, [isCapturing]);
|
|
58
|
+
}, [isCapturing, isCameraReady]);
|
|
53
59
|
|
|
54
60
|
// Handle retake
|
|
55
61
|
const handleRetake = useCallback(() => {
|
|
@@ -147,74 +153,75 @@ export default function CameraScreen() {
|
|
|
147
153
|
<View className="flex-1 bg-black">
|
|
148
154
|
<CameraView
|
|
149
155
|
ref={cameraRef}
|
|
150
|
-
style={
|
|
156
|
+
style={StyleSheet.absoluteFill}
|
|
151
157
|
facing={facing}
|
|
152
158
|
mode="picture"
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
159
|
+
onCameraReady={() => setIsCameraReady(true)}
|
|
160
|
+
/>
|
|
161
|
+
|
|
162
|
+
{/* Header with step info */}
|
|
163
|
+
<View className="absolute inset-x-0 top-12 px-6">
|
|
164
|
+
<View className="rounded-xl bg-black/70 p-4">
|
|
165
|
+
<Text className="text-center text-lg font-bold text-white">
|
|
166
|
+
{params.stepLabel}
|
|
167
|
+
</Text>
|
|
168
|
+
<Text className="text-center text-sm text-white/80">
|
|
169
|
+
📸 {params.stepDescription}
|
|
170
|
+
</Text>
|
|
164
171
|
</View>
|
|
172
|
+
</View>
|
|
165
173
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
</View>
|
|
174
|
+
{/* Frame guide overlay */}
|
|
175
|
+
<View className="absolute inset-0 items-center justify-center">
|
|
176
|
+
<View className="relative size-80">
|
|
177
|
+
{/* Frame */}
|
|
178
|
+
<View className="absolute inset-0 rounded-2xl border-2 border-white/60" />
|
|
179
|
+
|
|
180
|
+
{/* Corner guides */}
|
|
181
|
+
<View className="absolute left-2 top-2 size-6 border-l-4 border-t-4 border-white" />
|
|
182
|
+
<View className="absolute right-2 top-2 size-6 border-r-4 border-t-4 border-white" />
|
|
183
|
+
<View className="absolute bottom-2 left-2 size-6 border-b-4 border-l-4 border-white" />
|
|
184
|
+
<View className="absolute bottom-2 right-2 size-6 border-b-4 border-r-4 border-white" />
|
|
185
|
+
|
|
186
|
+
{/* Center guide */}
|
|
187
|
+
<View className="absolute inset-0 items-center justify-center">
|
|
188
|
+
<View className="h-1 w-8 bg-white/60" />
|
|
189
|
+
<View className="absolute h-8 w-1 bg-white/60" />
|
|
183
190
|
</View>
|
|
184
191
|
</View>
|
|
192
|
+
</View>
|
|
185
193
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
</CameraView>
|
|
194
|
+
{/* Controls */}
|
|
195
|
+
<View className="absolute inset-x-0 bottom-12 flex-row items-center justify-center px-6">
|
|
196
|
+
{/* Close button */}
|
|
197
|
+
<TouchableOpacity
|
|
198
|
+
onPress={() => router.back()}
|
|
199
|
+
className="absolute left-6 size-12 items-center justify-center rounded-full bg-black/50"
|
|
200
|
+
>
|
|
201
|
+
<Text className="text-xl text-white">✕</Text>
|
|
202
|
+
</TouchableOpacity>
|
|
203
|
+
|
|
204
|
+
{/* Shutter button */}
|
|
205
|
+
<TouchableOpacity
|
|
206
|
+
onPress={handleTakePhoto}
|
|
207
|
+
disabled={isCapturing || !isCameraReady}
|
|
208
|
+
className="size-20 items-center justify-center rounded-full border-4 border-white bg-white/20"
|
|
209
|
+
>
|
|
210
|
+
{isCapturing ? (
|
|
211
|
+
<ActivityIndicator size="large" color="white" />
|
|
212
|
+
) : (
|
|
213
|
+
<View className="size-16 rounded-full bg-white" />
|
|
214
|
+
)}
|
|
215
|
+
</TouchableOpacity>
|
|
216
|
+
|
|
217
|
+
{/* Flip camera button */}
|
|
218
|
+
<TouchableOpacity
|
|
219
|
+
onPress={toggleCameraFacing}
|
|
220
|
+
className="absolute right-6 size-12 items-center justify-center rounded-full bg-black/50"
|
|
221
|
+
>
|
|
222
|
+
<MaterialIcons name="cameraswitch" size={24} color="white" />
|
|
223
|
+
</TouchableOpacity>
|
|
224
|
+
</View>
|
|
218
225
|
</View>
|
|
219
226
|
</>
|
|
220
227
|
);
|
package/recipes/image-analysis/apps/native/src/features/image-analyzer/app/image-capture-screen.tsx
CHANGED
|
@@ -1,19 +1,17 @@
|
|
|
1
1
|
import * as ImagePicker from 'expo-image-picker';
|
|
2
2
|
import { Stack, useRouter } from 'expo-router';
|
|
3
|
-
import React, { useCallback, useEffect
|
|
3
|
+
import React, { useCallback, useEffect } from 'react';
|
|
4
4
|
import { Alert, ScrollView, TouchableOpacity, View } from 'react-native';
|
|
5
5
|
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
|
6
6
|
|
|
7
7
|
import { Button, Image, Text } from '@/components/ui';
|
|
8
8
|
import type { AnalysisFlowConfig } from '@/features/image-analyzer/config/master-analysis-config';
|
|
9
|
-
import {
|
|
9
|
+
import {
|
|
10
|
+
useCameraCaptureStore,
|
|
11
|
+
useImageCaptureFlowStore,
|
|
12
|
+
} from '@/features/image-analyzer/hooks/use-image-analysis';
|
|
10
13
|
import { translate } from '@/lib';
|
|
11
14
|
|
|
12
|
-
export type CapturedImage = {
|
|
13
|
-
uri: string;
|
|
14
|
-
stepId: string;
|
|
15
|
-
};
|
|
16
|
-
|
|
17
15
|
type ImageCaptureScreenProps = {
|
|
18
16
|
config: AnalysisFlowConfig;
|
|
19
17
|
};
|
|
@@ -26,13 +24,27 @@ export function ImageCaptureScreen({ config }: ImageCaptureScreenProps) {
|
|
|
26
24
|
const router = useRouter();
|
|
27
25
|
const { capturedImageUri, stepId, clearCapturedImage } =
|
|
28
26
|
useCameraCaptureStore();
|
|
29
|
-
const
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
27
|
+
const {
|
|
28
|
+
currentStepIndex,
|
|
29
|
+
capturedImages,
|
|
30
|
+
initFlow,
|
|
31
|
+
setCurrentStepIndex,
|
|
32
|
+
setCapturedImageForStep,
|
|
33
|
+
} = useImageCaptureFlowStore();
|
|
34
|
+
|
|
35
|
+
useEffect(() => {
|
|
36
|
+
initFlow(config.id, config.photoSteps.length);
|
|
37
|
+
}, [config.id, config.photoSteps.length, initFlow]);
|
|
33
38
|
|
|
34
|
-
const
|
|
35
|
-
|
|
39
|
+
const stepImages =
|
|
40
|
+
capturedImages.length === config.photoSteps.length
|
|
41
|
+
? capturedImages
|
|
42
|
+
: new Array(config.photoSteps.length).fill(null);
|
|
43
|
+
const safeStepIndex =
|
|
44
|
+
currentStepIndex < config.photoSteps.length ? currentStepIndex : 0;
|
|
45
|
+
const currentStep = config.photoSteps[safeStepIndex];
|
|
46
|
+
const activeStepIndex = safeStepIndex;
|
|
47
|
+
const allImagesCaptured = stepImages.every((img) => img !== null);
|
|
36
48
|
|
|
37
49
|
// Handle incoming captured image from camera screen via Zustand store
|
|
38
50
|
useEffect(() => {
|
|
@@ -49,13 +61,9 @@ export function ImageCaptureScreen({ config }: ImageCaptureScreenProps) {
|
|
|
49
61
|
}
|
|
50
62
|
|
|
51
63
|
// Update the captured images array
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
uri: capturedImageUri,
|
|
56
|
-
stepId: stepId,
|
|
57
|
-
};
|
|
58
|
-
return newCapturedImages;
|
|
64
|
+
setCapturedImageForStep(capturedStepIndex, {
|
|
65
|
+
uri: capturedImageUri,
|
|
66
|
+
stepId: stepId,
|
|
59
67
|
});
|
|
60
68
|
|
|
61
69
|
// Only auto-advance if we're currently viewing the step we just captured
|
|
@@ -72,7 +80,14 @@ export function ImageCaptureScreen({ config }: ImageCaptureScreenProps) {
|
|
|
72
80
|
|
|
73
81
|
clearCapturedImage();
|
|
74
82
|
}
|
|
75
|
-
}, [
|
|
83
|
+
}, [
|
|
84
|
+
capturedImageUri,
|
|
85
|
+
stepId,
|
|
86
|
+
clearCapturedImage,
|
|
87
|
+
config.photoSteps,
|
|
88
|
+
setCapturedImageForStep,
|
|
89
|
+
setCurrentStepIndex,
|
|
90
|
+
]);
|
|
76
91
|
|
|
77
92
|
// Handle camera launch
|
|
78
93
|
const handleOpenCamera = useCallback(async () => {
|
|
@@ -124,35 +139,40 @@ export function ImageCaptureScreen({ config }: ImageCaptureScreenProps) {
|
|
|
124
139
|
if (!result.canceled && result.assets[0]) {
|
|
125
140
|
const imageUri = result.assets[0].uri;
|
|
126
141
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
uri: imageUri,
|
|
131
|
-
stepId: currentStep.id,
|
|
132
|
-
};
|
|
133
|
-
return newCapturedImages;
|
|
142
|
+
setCapturedImageForStep(activeStepIndex, {
|
|
143
|
+
uri: imageUri,
|
|
144
|
+
stepId: currentStep.id,
|
|
134
145
|
});
|
|
135
146
|
|
|
136
147
|
// Auto-advance to next step if not last
|
|
137
|
-
const isLastStep =
|
|
148
|
+
const isLastStep = activeStepIndex === config.photoSteps.length - 1;
|
|
138
149
|
if (!isLastStep) {
|
|
139
|
-
setCurrentStepIndex(
|
|
150
|
+
setCurrentStepIndex(activeStepIndex + 1);
|
|
140
151
|
}
|
|
141
152
|
}
|
|
142
153
|
} catch (error) {
|
|
143
154
|
console.error('Error selecting from gallery:', error);
|
|
144
155
|
Alert.alert('Error', 'Failed to select image. Please try again.');
|
|
145
156
|
}
|
|
146
|
-
}, [
|
|
157
|
+
}, [
|
|
158
|
+
activeStepIndex,
|
|
159
|
+
currentStep.id,
|
|
160
|
+
config.photoSteps.length,
|
|
161
|
+
setCapturedImageForStep,
|
|
162
|
+
setCurrentStepIndex,
|
|
163
|
+
]);
|
|
147
164
|
|
|
148
165
|
// Handle step switching - allow clicking any step freely
|
|
149
|
-
const handleStepSwitch = useCallback(
|
|
150
|
-
|
|
151
|
-
|
|
166
|
+
const handleStepSwitch = useCallback(
|
|
167
|
+
(targetIndex: number) => {
|
|
168
|
+
setCurrentStepIndex(targetIndex);
|
|
169
|
+
},
|
|
170
|
+
[setCurrentStepIndex],
|
|
171
|
+
);
|
|
152
172
|
|
|
153
173
|
// Handle continue - either to options or directly to loading based on config
|
|
154
174
|
const handleContinue = useCallback(() => {
|
|
155
|
-
const imageUris =
|
|
175
|
+
const imageUris = stepImages.map((img) => img!.uri);
|
|
156
176
|
|
|
157
177
|
if (config.skipOptionsScreen) {
|
|
158
178
|
// Go directly to loading with default preferences
|
|
@@ -176,7 +196,7 @@ export function ImageCaptureScreen({ config }: ImageCaptureScreenProps) {
|
|
|
176
196
|
},
|
|
177
197
|
});
|
|
178
198
|
}
|
|
179
|
-
}, [
|
|
199
|
+
}, [stepImages, router, config]);
|
|
180
200
|
|
|
181
201
|
const insets = useSafeAreaInsets();
|
|
182
202
|
|
|
@@ -203,16 +223,16 @@ export function ImageCaptureScreen({ config }: ImageCaptureScreenProps) {
|
|
|
203
223
|
<TouchableOpacity
|
|
204
224
|
onPress={() => handleStepSwitch(index)}
|
|
205
225
|
className={`relative z-10 size-16 items-center justify-center rounded-full border-2 ${
|
|
206
|
-
|
|
226
|
+
stepImages[index] !== null
|
|
207
227
|
? 'border-green-500 bg-green-500'
|
|
208
|
-
: index ===
|
|
228
|
+
: index === activeStepIndex
|
|
209
229
|
? 'border-primary-600 bg-primary-600'
|
|
210
230
|
: 'border-neutral-300 bg-white dark:border-neutral-600 dark:bg-neutral-800'
|
|
211
231
|
}`}
|
|
212
232
|
>
|
|
213
|
-
{
|
|
233
|
+
{stepImages[index] !== null ? (
|
|
214
234
|
<Text className="text-2xl font-bold text-white">✓</Text>
|
|
215
|
-
) : index ===
|
|
235
|
+
) : index === activeStepIndex ? (
|
|
216
236
|
<Text className="text-xl font-semibold text-white">
|
|
217
237
|
{index + 1}
|
|
218
238
|
</Text>
|
|
@@ -227,9 +247,9 @@ export function ImageCaptureScreen({ config }: ImageCaptureScreenProps) {
|
|
|
227
247
|
{index < config.photoSteps.length - 1 && (
|
|
228
248
|
<View
|
|
229
249
|
className={`mx-3 h-0.5 flex-1 ${
|
|
230
|
-
|
|
250
|
+
stepImages[index] !== null
|
|
231
251
|
? 'bg-green-500'
|
|
232
|
-
: index <
|
|
252
|
+
: index < activeStepIndex
|
|
233
253
|
? 'bg-primary-600'
|
|
234
254
|
: 'bg-neutral-300 dark:bg-neutral-600'
|
|
235
255
|
}`}
|
|
@@ -274,8 +294,8 @@ export function ImageCaptureScreen({ config }: ImageCaptureScreenProps) {
|
|
|
274
294
|
{/* Guide Image or Captured Image */}
|
|
275
295
|
<Image
|
|
276
296
|
source={
|
|
277
|
-
|
|
278
|
-
? { uri:
|
|
297
|
+
stepImages[activeStepIndex]
|
|
298
|
+
? { uri: stepImages[activeStepIndex]!.uri }
|
|
279
299
|
: currentStep.guideImage
|
|
280
300
|
}
|
|
281
301
|
className="aspect-square w-full rounded-3xl"
|
|
@@ -294,9 +314,7 @@ export function ImageCaptureScreen({ config }: ImageCaptureScreenProps) {
|
|
|
294
314
|
<View className="mb-1 flex-row gap-x-3">
|
|
295
315
|
<Button
|
|
296
316
|
onPress={handleOpenCamera}
|
|
297
|
-
label={
|
|
298
|
-
capturedImages[currentStepIndex] ? 'Retake Photo' : 'Take Photo'
|
|
299
|
-
}
|
|
317
|
+
label={stepImages[activeStepIndex] ? 'Retake Photo' : 'Take Photo'}
|
|
300
318
|
className="flex-1 bg-primary-600"
|
|
301
319
|
textClassName="text-white font-semibold"
|
|
302
320
|
/>
|
package/recipes/image-analysis/apps/native/src/features/image-analyzer/app/loading-screen.tsx
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
import type { Id } from '@vibefast/backend/_generated/dataModel';
|
|
2
2
|
import { Stack, useLocalSearchParams, useRouter } from 'expo-router';
|
|
3
|
-
import React, {
|
|
3
|
+
import React, {
|
|
4
|
+
useCallback,
|
|
5
|
+
useEffect,
|
|
6
|
+
useMemo,
|
|
7
|
+
useRef,
|
|
8
|
+
useState,
|
|
9
|
+
} from 'react';
|
|
4
10
|
import { ActivityIndicator, Alert, Animated, View } from 'react-native';
|
|
5
11
|
|
|
6
12
|
import { Button, Text } from '@/components/ui';
|
|
@@ -42,6 +48,10 @@ export function LoadingScreen({ config }: LoadingScreenProps) {
|
|
|
42
48
|
const [analysisId, setAnalysisId] = useState<Id<'imageAnalyses'> | undefined>(
|
|
43
49
|
undefined,
|
|
44
50
|
);
|
|
51
|
+
const hasStartedRef = useRef(false);
|
|
52
|
+
const isRunningRef = useRef(false);
|
|
53
|
+
const hasHandledFailureRef = useRef(false);
|
|
54
|
+
const hasNavigatedRef = useRef(false);
|
|
45
55
|
|
|
46
56
|
const analyzeImages = useAnalyzeImages();
|
|
47
57
|
|
|
@@ -50,6 +60,12 @@ export function LoadingScreen({ config }: LoadingScreenProps) {
|
|
|
50
60
|
|
|
51
61
|
// Handle analysis creation
|
|
52
62
|
const handleCreateAnalysis = useCallback(async () => {
|
|
63
|
+
if (isRunningRef.current) {
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
isRunningRef.current = true;
|
|
68
|
+
hasHandledFailureRef.current = false;
|
|
53
69
|
try {
|
|
54
70
|
setCurrentStep('Converting images...');
|
|
55
71
|
setProgress(10);
|
|
@@ -60,6 +76,7 @@ export function LoadingScreen({ config }: LoadingScreenProps) {
|
|
|
60
76
|
goal: analysisGoal,
|
|
61
77
|
feedbackStyle,
|
|
62
78
|
},
|
|
79
|
+
analysisConfigId: config.convexAnalysisConfigId,
|
|
63
80
|
onProgress: (step: string, progressValue: number) => {
|
|
64
81
|
setCurrentStep(step);
|
|
65
82
|
setProgress(progressValue);
|
|
@@ -90,11 +107,25 @@ export function LoadingScreen({ config }: LoadingScreenProps) {
|
|
|
90
107
|
},
|
|
91
108
|
],
|
|
92
109
|
);
|
|
110
|
+
} finally {
|
|
111
|
+
isRunningRef.current = false;
|
|
93
112
|
}
|
|
94
|
-
}, [
|
|
113
|
+
}, [
|
|
114
|
+
imageUris,
|
|
115
|
+
analysisGoal,
|
|
116
|
+
feedbackStyle,
|
|
117
|
+
analyzeImages,
|
|
118
|
+
router,
|
|
119
|
+
config.convexAnalysisConfigId,
|
|
120
|
+
]);
|
|
95
121
|
|
|
96
122
|
// Start analysis on mount
|
|
97
123
|
useEffect(() => {
|
|
124
|
+
if (hasStartedRef.current) {
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
hasStartedRef.current = true;
|
|
98
129
|
handleCreateAnalysis();
|
|
99
130
|
}, [handleCreateAnalysis]);
|
|
100
131
|
|
|
@@ -107,6 +138,11 @@ export function LoadingScreen({ config }: LoadingScreenProps) {
|
|
|
107
138
|
setProgress(80);
|
|
108
139
|
break;
|
|
109
140
|
case 'completed':
|
|
141
|
+
if (hasNavigatedRef.current) {
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
hasNavigatedRef.current = true;
|
|
110
146
|
setCurrentStep('Analysis complete!');
|
|
111
147
|
setProgress(100);
|
|
112
148
|
// Navigate to results after a brief delay
|
|
@@ -123,6 +159,11 @@ export function LoadingScreen({ config }: LoadingScreenProps) {
|
|
|
123
159
|
}, 1000);
|
|
124
160
|
break;
|
|
125
161
|
case 'failed':
|
|
162
|
+
if (hasHandledFailureRef.current) {
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
hasHandledFailureRef.current = true;
|
|
126
167
|
setCurrentStep('Analysis failed');
|
|
127
168
|
setProgress(0);
|
|
128
169
|
Alert.alert(
|
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
import type { Id } from '@vibefast/backend/_generated/dataModel';
|
|
2
2
|
import { Stack, useLocalSearchParams, useRouter } from 'expo-router';
|
|
3
|
-
import React, {
|
|
3
|
+
import React, {
|
|
4
|
+
useCallback,
|
|
5
|
+
useEffect,
|
|
6
|
+
useMemo,
|
|
7
|
+
useRef,
|
|
8
|
+
useState,
|
|
9
|
+
} from 'react';
|
|
4
10
|
import { ActivityIndicator, Alert, View } from 'react-native';
|
|
5
11
|
|
|
6
12
|
import { Button, Text } from '@/components/ui';
|
|
@@ -40,6 +46,10 @@ export default function LoadingScreen() {
|
|
|
40
46
|
const [analysisId, setAnalysisId] = useState<Id<'imageAnalyses'> | undefined>(
|
|
41
47
|
undefined,
|
|
42
48
|
);
|
|
49
|
+
const hasStartedRef = useRef(false);
|
|
50
|
+
const isRunningRef = useRef(false);
|
|
51
|
+
const hasHandledFailureRef = useRef(false);
|
|
52
|
+
const hasNavigatedRef = useRef(false);
|
|
43
53
|
|
|
44
54
|
const analyzeImages = useAnalyzeImages();
|
|
45
55
|
|
|
@@ -48,6 +58,12 @@ export default function LoadingScreen() {
|
|
|
48
58
|
|
|
49
59
|
// Handle analysis creation
|
|
50
60
|
const handleCreateAnalysis = useCallback(async () => {
|
|
61
|
+
if (isRunningRef.current) {
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
isRunningRef.current = true;
|
|
66
|
+
hasHandledFailureRef.current = false;
|
|
51
67
|
try {
|
|
52
68
|
setCurrentStep('Converting images...');
|
|
53
69
|
setProgress(10);
|
|
@@ -88,11 +104,18 @@ export default function LoadingScreen() {
|
|
|
88
104
|
},
|
|
89
105
|
],
|
|
90
106
|
);
|
|
107
|
+
} finally {
|
|
108
|
+
isRunningRef.current = false;
|
|
91
109
|
}
|
|
92
110
|
}, [imageUris, analysisGoal, feedbackStyle, analyzeImages, router]);
|
|
93
111
|
|
|
94
112
|
// Start analysis on mount
|
|
95
113
|
useEffect(() => {
|
|
114
|
+
if (hasStartedRef.current) {
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
hasStartedRef.current = true;
|
|
96
119
|
handleCreateAnalysis();
|
|
97
120
|
}, [handleCreateAnalysis]);
|
|
98
121
|
|
|
@@ -105,6 +128,11 @@ export default function LoadingScreen() {
|
|
|
105
128
|
setProgress(80);
|
|
106
129
|
break;
|
|
107
130
|
case 'completed':
|
|
131
|
+
if (hasNavigatedRef.current) {
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
hasNavigatedRef.current = true;
|
|
108
136
|
setCurrentStep('Analysis complete!');
|
|
109
137
|
setProgress(100);
|
|
110
138
|
// Navigate to results after a brief delay
|
|
@@ -120,6 +148,11 @@ export default function LoadingScreen() {
|
|
|
120
148
|
}, 1000);
|
|
121
149
|
break;
|
|
122
150
|
case 'failed':
|
|
151
|
+
if (hasHandledFailureRef.current) {
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
hasHandledFailureRef.current = true;
|
|
123
156
|
setCurrentStep('Analysis failed');
|
|
124
157
|
setProgress(0);
|
|
125
158
|
Alert.alert(
|
package/recipes/image-analysis/apps/native/src/features/image-analyzer/hooks/use-image-analysis.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Id } from '@vibefast/backend/_generated/dataModel';
|
|
2
|
-
import * as FileSystem from 'expo-file-system';
|
|
2
|
+
import * as FileSystem from 'expo-file-system/legacy';
|
|
3
3
|
import * as ImageManipulator from 'expo-image-manipulator';
|
|
4
4
|
import { useCallback } from 'react';
|
|
5
5
|
import { create } from 'zustand';
|
|
@@ -28,6 +28,7 @@ export type AnalyzeImagesParams = {
|
|
|
28
28
|
goal: string;
|
|
29
29
|
feedbackStyle: string;
|
|
30
30
|
};
|
|
31
|
+
analysisConfigId?: string;
|
|
31
32
|
onProgress?: (step: string, progress: number) => void;
|
|
32
33
|
};
|
|
33
34
|
|
|
@@ -40,6 +41,85 @@ export type CreateImageAnalysisParams = {
|
|
|
40
41
|
onProgress?: (step: string, progress: number) => void;
|
|
41
42
|
};
|
|
42
43
|
|
|
44
|
+
export type CapturedImage = {
|
|
45
|
+
uri: string;
|
|
46
|
+
stepId: string;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
type StepIndexUpdater = number | ((current: number) => number);
|
|
50
|
+
|
|
51
|
+
type ImageCaptureFlowState = {
|
|
52
|
+
flowId: string | null;
|
|
53
|
+
stepsCount: number;
|
|
54
|
+
currentStepIndex: number;
|
|
55
|
+
capturedImages: (CapturedImage | null)[];
|
|
56
|
+
initFlow: (flowId: string, stepsCount: number) => void;
|
|
57
|
+
setCurrentStepIndex: (updater: StepIndexUpdater) => void;
|
|
58
|
+
setCapturedImageForStep: (stepIndex: number, image: CapturedImage) => void;
|
|
59
|
+
clearFlow: () => void;
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
export const useImageCaptureFlowStore = create<ImageCaptureFlowState>(
|
|
63
|
+
(set) => ({
|
|
64
|
+
flowId: null,
|
|
65
|
+
stepsCount: 0,
|
|
66
|
+
currentStepIndex: 0,
|
|
67
|
+
capturedImages: [],
|
|
68
|
+
initFlow: (flowId, stepsCount) =>
|
|
69
|
+
set((state) => {
|
|
70
|
+
if (
|
|
71
|
+
state.flowId === flowId &&
|
|
72
|
+
state.stepsCount === stepsCount &&
|
|
73
|
+
state.capturedImages.length === stepsCount
|
|
74
|
+
) {
|
|
75
|
+
return state;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return {
|
|
79
|
+
flowId,
|
|
80
|
+
stepsCount,
|
|
81
|
+
currentStepIndex: 0,
|
|
82
|
+
capturedImages: new Array(stepsCount).fill(null),
|
|
83
|
+
};
|
|
84
|
+
}),
|
|
85
|
+
setCurrentStepIndex: (updater) =>
|
|
86
|
+
set((state) => {
|
|
87
|
+
const nextIndex =
|
|
88
|
+
typeof updater === 'function'
|
|
89
|
+
? updater(state.currentStepIndex)
|
|
90
|
+
: updater;
|
|
91
|
+
if (state.stepsCount <= 0) {
|
|
92
|
+
return { currentStepIndex: nextIndex };
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const maxIndex = state.stepsCount - 1;
|
|
96
|
+
const clampedIndex = Math.max(0, Math.min(nextIndex, maxIndex));
|
|
97
|
+
return { currentStepIndex: clampedIndex };
|
|
98
|
+
}),
|
|
99
|
+
setCapturedImageForStep: (stepIndex, image) =>
|
|
100
|
+
set((state) => {
|
|
101
|
+
const nextImages =
|
|
102
|
+
state.stepsCount > 0 ? new Array(state.stepsCount).fill(null) : [];
|
|
103
|
+
|
|
104
|
+
if (state.capturedImages.length === state.stepsCount) {
|
|
105
|
+
state.capturedImages.forEach((item, index) => {
|
|
106
|
+
nextImages[index] = item;
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
nextImages[stepIndex] = image;
|
|
111
|
+
return { capturedImages: nextImages };
|
|
112
|
+
}),
|
|
113
|
+
clearFlow: () =>
|
|
114
|
+
set({
|
|
115
|
+
flowId: null,
|
|
116
|
+
stepsCount: 0,
|
|
117
|
+
currentStepIndex: 0,
|
|
118
|
+
capturedImages: [],
|
|
119
|
+
}),
|
|
120
|
+
}),
|
|
121
|
+
);
|
|
122
|
+
|
|
43
123
|
/**
|
|
44
124
|
* Hook for analyzing images using the new background processing flow.
|
|
45
125
|
*/
|
|
@@ -50,7 +130,7 @@ export function useAnalyzeImages() {
|
|
|
50
130
|
async (
|
|
51
131
|
params: AnalyzeImagesParams,
|
|
52
132
|
): Promise<Id<'imageAnalyses'> | null> => {
|
|
53
|
-
const { imageUris, preferences, onProgress } = params;
|
|
133
|
+
const { imageUris, preferences, analysisConfigId, onProgress } = params;
|
|
54
134
|
|
|
55
135
|
try {
|
|
56
136
|
onProgress?.('Optimizing images...', 15);
|
|
@@ -92,6 +172,7 @@ export function useAnalyzeImages() {
|
|
|
92
172
|
const analysisId = await analyzeAction({
|
|
93
173
|
imagesAsBase64: base64Images,
|
|
94
174
|
preferences,
|
|
175
|
+
analysisConfigId,
|
|
95
176
|
});
|
|
96
177
|
|
|
97
178
|
onProgress?.('Analysis in progress...', 50);
|