vibefast-cli 1.1.5 โ 1.3.0
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/CHANGELOG.md +32 -0
- package/README.md +63 -169
- package/dist/__tests__/recipes.test.js +89 -85
- package/dist/__tests__/recipes.test.js.map +1 -1
- package/dist/commands/add.d.ts +1 -1
- package/dist/commands/add.d.ts.map +1 -1
- package/dist/commands/add.js +576 -588
- package/dist/commands/add.js.map +1 -1
- package/dist/commands/checklist.d.ts +1 -1
- package/dist/commands/checklist.d.ts.map +1 -1
- package/dist/commands/checklist.js +40 -39
- package/dist/commands/checklist.js.map +1 -1
- package/dist/commands/doctor.d.ts +1 -1
- package/dist/commands/doctor.js +22 -22
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/env.d.ts +1 -1
- package/dist/commands/env.d.ts.map +1 -1
- package/dist/commands/env.js +58 -53
- package/dist/commands/env.js.map +1 -1
- package/dist/commands/health.d.ts +1 -1
- package/dist/commands/health.d.ts.map +1 -1
- package/dist/commands/health.js +101 -93
- package/dist/commands/health.js.map +1 -1
- package/dist/commands/init.d.ts +1 -1
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +416 -296
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/remove.d.ts +1 -1
- package/dist/commands/remove.d.ts.map +1 -1
- package/dist/commands/remove.js +77 -64
- package/dist/commands/remove.js.map +1 -1
- package/dist/commands/status.d.ts +1 -1
- package/dist/commands/status.d.ts.map +1 -1
- package/dist/commands/status.js +15 -14
- package/dist/commands/status.js.map +1 -1
- package/dist/core/__tests__/detect.test.js +68 -34
- package/dist/core/__tests__/detect.test.js.map +1 -1
- package/dist/core/ast.d.ts +14 -0
- package/dist/core/ast.d.ts.map +1 -0
- package/dist/core/ast.js +239 -0
- package/dist/core/ast.js.map +1 -0
- package/dist/core/codemod.d.ts.map +1 -1
- package/dist/core/codemod.js +62 -44
- package/dist/core/codemod.js.map +1 -1
- package/dist/core/config.d.ts +10 -0
- package/dist/core/config.d.ts.map +1 -0
- package/dist/core/config.js +51 -0
- package/dist/core/config.js.map +1 -0
- package/dist/core/detect.d.ts +8 -2
- package/dist/core/detect.d.ts.map +1 -1
- package/dist/core/detect.js +52 -21
- package/dist/core/detect.js.map +1 -1
- package/dist/core/errors.d.ts.map +1 -1
- package/dist/core/errors.js +9 -8
- package/dist/core/errors.js.map +1 -1
- package/dist/core/exec.d.ts +16 -0
- package/dist/core/exec.d.ts.map +1 -0
- package/dist/core/exec.js +48 -0
- package/dist/core/exec.js.map +1 -0
- package/dist/core/manualSteps.d.ts +7 -0
- package/dist/core/manualSteps.d.ts.map +1 -0
- package/dist/core/manualSteps.js +59 -0
- package/dist/core/manualSteps.js.map +1 -0
- package/dist/core/paths.d.ts +3 -1
- package/dist/core/paths.d.ts.map +1 -1
- package/dist/core/paths.js +14 -10
- package/dist/core/paths.js.map +1 -1
- package/dist/core/spinner.d.ts +1 -1
- package/dist/core/spinner.d.ts.map +1 -1
- package/dist/core/spinner.js +38 -8
- package/dist/core/spinner.js.map +1 -1
- package/dist/core/vosk.d.ts.map +1 -1
- package/dist/core/vosk.js +50 -39
- package/dist/core/vosk.js.map +1 -1
- package/docs/manual-testing.md +91 -0
- package/package.json +6 -3
- package/recipes/audio-recorder/apps/native/src/app/audio-recorder/index.tsx +5 -0
- package/recipes/audio-recorder/recipe.json +3 -3
- package/recipes/audio-recorder-supabase/apps/native/src/features/audio-recorder/components/audio-player.tsx +301 -0
- package/recipes/audio-recorder-supabase/apps/native/src/features/audio-recorder/components/audio-recorder.tsx +373 -0
- package/recipes/audio-recorder-supabase/apps/native/src/features/audio-recorder/components/audio-waveform.tsx +270 -0
- package/recipes/audio-recorder-supabase/apps/native/src/features/audio-recorder/components/index.ts +4 -0
- package/recipes/audio-recorder-supabase/apps/native/src/features/audio-recorder/components/recording-list.tsx +89 -0
- package/recipes/audio-recorder-supabase/apps/native/src/features/audio-recorder/demo/audio-player-demo.tsx +66 -0
- package/recipes/audio-recorder-supabase/apps/native/src/features/audio-recorder/demo/audio-recorder-cloud.tsx +68 -0
- package/recipes/audio-recorder-supabase/apps/native/src/features/audio-recorder/demo/audio-recorder-interview.tsx +102 -0
- package/recipes/audio-recorder-supabase/apps/native/src/features/audio-recorder/demo/basic.tsx +27 -0
- package/recipes/audio-recorder-supabase/apps/native/src/features/audio-recorder/demo/index.ts +5 -0
- package/recipes/audio-recorder-supabase/apps/native/src/features/audio-recorder/demo/with-recording-list-demo.tsx +82 -0
- package/recipes/audio-recorder-supabase/packages/backend/src/services/recordings.ts +369 -0
- package/recipes/audio-recorder-supabase/packages/backend/supabase/migrations/recordings.sql +70 -0
- package/recipes/audio-recorder-supabase/recipe.json +52 -0
- package/recipes/audio-recorder-supabase@latest.zip +0 -0
- package/recipes/audio-recorder@latest.zip +0 -0
- package/recipes/charts/apps/native/src/features/charts/components/bar-chart.tsx +3 -3
- package/recipes/charts/apps/native/src/features/charts/components/candlestick-chart.tsx +2 -2
- package/recipes/charts/apps/native/src/features/charts/components/chart-card.tsx +5 -5
- package/recipes/charts/apps/native/src/features/charts/components/column-chart.tsx +3 -3
- package/recipes/charts/apps/native/src/features/charts/components/doughnut-chart.tsx +20 -4
- package/recipes/charts/apps/native/src/features/charts/components/line-chart.tsx +7 -6
- package/recipes/charts/apps/native/src/features/charts/components/radar-chart.tsx +6 -4
- package/recipes/charts/apps/native/src/features/charts/components/radial-bar-chart.tsx +1 -1
- package/recipes/charts/apps/native/src/features/charts/components/stacked-bar-chart.tsx +5 -4
- package/recipes/charts/recipe.json +4 -13
- package/recipes/charts@latest.zip +0 -0
- package/recipes/chatbot/apps/native/src/app/chatbot/index.tsx +1 -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 +86 -53
- package/recipes/chatbot/recipe.json +26 -92
- package/recipes/chatbot-supabase/apps/native/src/api-client/supabase/chatbot.ts +515 -0
- package/recipes/chatbot-supabase/apps/native/src/features/chatbot/app/index.tsx +257 -0
- package/recipes/chatbot-supabase/apps/native/src/features/chatbot/components/chat-header-buttons.tsx +59 -0
- package/recipes/chatbot-supabase/apps/native/src/features/chatbot/components/chat-input-bar.tsx +485 -0
- package/recipes/chatbot-supabase/apps/native/src/features/chatbot/components/chat-markdown.tsx +575 -0
- package/recipes/chatbot-supabase/apps/native/src/features/chatbot/components/chat-message-bubble.tsx +223 -0
- package/recipes/chatbot-supabase/apps/native/src/features/chatbot/components/chat-settings-modal.tsx +161 -0
- package/recipes/chatbot-supabase/apps/native/src/features/chatbot/components/image-preview-list.tsx +116 -0
- package/recipes/chatbot-supabase/apps/native/src/features/chatbot/components/markdown/code-block.tsx +165 -0
- package/recipes/chatbot-supabase/apps/native/src/features/chatbot/components/markdown/index.ts +10 -0
- package/recipes/chatbot-supabase/apps/native/src/features/chatbot/components/markdown/table-renderer.tsx +129 -0
- package/recipes/chatbot-supabase/apps/native/src/features/chatbot/components/message-error-boundary.tsx +78 -0
- package/recipes/chatbot-supabase/apps/native/src/features/chatbot/components/message-list.tsx +170 -0
- package/recipes/chatbot-supabase/apps/native/src/features/chatbot/components/model-selector.tsx +283 -0
- package/recipes/chatbot-supabase/apps/native/src/features/chatbot/components/report-content-modal.tsx +188 -0
- package/recipes/chatbot-supabase/apps/native/src/features/chatbot/components/suggested-messages.tsx +67 -0
- package/recipes/chatbot-supabase/apps/native/src/features/chatbot/constants/models.ts +20 -0
- package/recipes/chatbot-supabase/apps/native/src/features/chatbot/constants/report-reasons.ts +9 -0
- package/recipes/chatbot-supabase/apps/native/src/features/chatbot/hooks/use-attachment-cache.ts +142 -0
- package/recipes/chatbot-supabase/apps/native/src/features/chatbot/hooks/use-chat-config.ts +458 -0
- package/recipes/chatbot-supabase/apps/native/src/features/chatbot/hooks/use-chat-handlers.ts +429 -0
- package/recipes/chatbot-supabase/apps/native/src/features/chatbot/hooks/use-chatbot-settings.ts +89 -0
- package/recipes/chatbot-supabase/apps/native/src/features/chatbot/hooks/use-conversation.ts +90 -0
- package/recipes/chatbot-supabase/apps/native/src/features/chatbot/hooks/use-image-picker.ts +122 -0
- package/recipes/chatbot-supabase/apps/native/src/features/chatbot/hooks/use-keyboard-coordinator.ts +161 -0
- package/recipes/chatbot-supabase/apps/native/src/features/chatbot/hooks/use-smart-scroll-manager.ts +213 -0
- package/recipes/chatbot-supabase/apps/native/src/features/chatbot/models/index.ts +86 -0
- package/recipes/chatbot-supabase/apps/native/src/features/chatbot/models/models.ts +162 -0
- package/recipes/chatbot-supabase/apps/native/src/features/chatbot/models/providers.ts +62 -0
- package/recipes/chatbot-supabase/apps/native/src/features/chatbot/models/types.ts +40 -0
- package/recipes/chatbot-supabase/apps/native/src/features/chatbot/services/file-uploader.ts +287 -0
- package/recipes/chatbot-supabase/apps/native/src/features/chatbot/services/message-handler-service.ts +189 -0
- package/recipes/chatbot-supabase/apps/native/src/features/chatbot/types/index.ts +70 -0
- package/recipes/chatbot-supabase/apps/native/src/features/chatbot/utils/chat-telemetry.ts +91 -0
- package/recipes/chatbot-supabase/packages/backend/src/services/conversations.ts +243 -0
- package/recipes/chatbot-supabase/packages/backend/src/services/messages.ts +327 -0
- package/recipes/chatbot-supabase/packages/backend/supabase/functions/chat-stream/index.ts +347 -0
- package/recipes/chatbot-supabase/packages/backend/supabase/migrations/chatbot.sql +104 -0
- package/recipes/chatbot-supabase/recipe.json +106 -0
- package/recipes/chatbot-supabase@latest.zip +0 -0
- package/recipes/chatbot.zip +0 -0
- package/recipes/chatbot@latest.zip +0 -0
- package/recipes/image-analysis/packages/backend/convex/imageAnalysis/index.ts +2 -2
- package/recipes/image-analysis/packages/backend/convex/imageAnalysis.ts +0 -1
- package/recipes/image-analysis/recipe.json +15 -55
- package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/app/analysis-options-screen.tsx +304 -0
- package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/app/camera.tsx +221 -0
- package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/app/image-capture-screen.tsx +333 -0
- package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/app/loading-screen.tsx +214 -0
- package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/app/loading.tsx +191 -0
- package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/app/results.tsx +137 -0
- package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/app/trait-details.tsx +172 -0
- package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/app/use-analysis-data.ts +160 -0
- package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/app/use-results-screen.ts +151 -0
- package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/components/results/achievement-badge.tsx +77 -0
- package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/components/results/achievement-card.tsx +75 -0
- package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/components/results/achievement-unlocked-modal.tsx +162 -0
- package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/components/results/achievements-section.tsx +44 -0
- package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/components/results/advice-list.tsx +42 -0
- package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/components/results/circular-progress.tsx +233 -0
- package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/components/results/content-card.tsx +38 -0
- package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/components/results/error-state.tsx +42 -0
- package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/components/results/index.ts +9 -0
- package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/components/results/loading-state.tsx +26 -0
- package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/components/results/profile-image.tsx +60 -0
- package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/components/results/results-header.tsx +62 -0
- package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/components/results/score-display.tsx +54 -0
- package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/components/results/share-options-modal.tsx +110 -0
- package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/components/results/traits-grid.tsx +74 -0
- package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/config/analysis-config.ts +80 -0
- package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/config/master-analysis-config.ts +157 -0
- package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/hooks/index.ts +1 -0
- package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/hooks/use-analysis.ts +38 -0
- package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/hooks/use-image-analysis.ts +208 -0
- package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/services/analysis-service.ts +262 -0
- package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/services/share-service.ts +176 -0
- package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/services/trait-details-service.ts +289 -0
- package/recipes/image-analysis-supabase/packages/backend/src/services/image-analyses.ts +132 -0
- package/recipes/image-analysis-supabase/packages/backend/supabase/functions/analyze-image/index.ts +312 -0
- package/recipes/image-analysis-supabase/packages/backend/supabase/migrations/image_analysis.sql +42 -0
- package/recipes/image-analysis-supabase/recipe.json +90 -0
- package/recipes/image-analysis-supabase@latest.zip +0 -0
- package/recipes/image-analysis@latest.zip +0 -0
- package/recipes/image-generator/apps/native/src/features/image-generator/app/index.tsx +16 -2
- package/recipes/image-generator/apps/native/src/features/image-generator/components/image-model-selector.tsx +11 -5
- package/recipes/image-generator/apps/native/src/features/image-generator/hooks/use-image-generator.ts +11 -5
- package/recipes/image-generator/packages/backend/convex/imageGeneration/index.ts +2 -2
- package/recipes/image-generator/recipe.json +16 -39
- package/recipes/image-generator-supabase/apps/native/src/features/image-generator/app/_layout.tsx +26 -0
- package/recipes/image-generator-supabase/apps/native/src/features/image-generator/app/gallery.tsx +217 -0
- package/recipes/image-generator-supabase/apps/native/src/features/image-generator/app/index.tsx +251 -0
- package/recipes/image-generator-supabase/apps/native/src/features/image-generator/components/gallery-image.tsx +25 -0
- package/recipes/image-generator-supabase/apps/native/src/features/image-generator/components/image-detail-modal.tsx +215 -0
- package/recipes/image-generator-supabase/apps/native/src/features/image-generator/components/image-model-selector.tsx +216 -0
- package/recipes/image-generator-supabase/apps/native/src/features/image-generator/components/image-placeholder.tsx +26 -0
- package/recipes/image-generator-supabase/apps/native/src/features/image-generator/hooks/use-image-gallery.ts +71 -0
- package/recipes/image-generator-supabase/apps/native/src/features/image-generator/hooks/use-image-generator-settings.ts +152 -0
- package/recipes/image-generator-supabase/apps/native/src/features/image-generator/hooks/use-image-generator.ts +103 -0
- package/recipes/image-generator-supabase/apps/native/src/features/image-generator/models/models.ts +66 -0
- package/recipes/image-generator-supabase/apps/native/src/features/image-generator/services/image-gallery-service.ts +96 -0
- package/recipes/image-generator-supabase/apps/native/src/features/image-generator/services/image-save-service.ts +120 -0
- package/recipes/image-generator-supabase/packages/backend/supabase/functions/generate-image/index.ts +291 -0
- package/recipes/image-generator-supabase/packages/backend/supabase/migrations/image_generator.sql +71 -0
- package/recipes/image-generator-supabase/recipe.json +86 -0
- package/recipes/image-generator-supabase@latest.zip +0 -0
- package/recipes/image-generator@latest.zip +0 -0
- package/recipes/ios-widget/recipe.json +15 -24
- package/recipes/ios-widget@latest.zip +0 -0
- package/recipes/onboarding/apps/native/src/features/onboarding/analytics/index.ts +9 -0
- package/recipes/onboarding/apps/native/src/features/onboarding/components/onboarding-with-analytics.tsx +141 -0
- package/recipes/onboarding/apps/native/src/features/onboarding/components/onboarding.tsx +173 -0
- package/recipes/onboarding/apps/native/src/features/onboarding/config/onboarding-flow-config.ts +189 -0
- package/recipes/onboarding/apps/native/src/features/onboarding/demo-one/app/index.tsx +42 -0
- package/recipes/onboarding/apps/native/src/features/onboarding/demo-one/data.ts +32 -0
- package/recipes/onboarding/apps/native/src/features/onboarding/expense-tracker/app/index.tsx +43 -0
- package/recipes/onboarding/apps/native/src/features/onboarding/expense-tracker/components/interactive-onboarding.tsx +222 -0
- package/recipes/onboarding/apps/native/src/features/onboarding/expense-tracker/components/steps/ai-tone-step.tsx +133 -0
- package/recipes/onboarding/apps/native/src/features/onboarding/expense-tracker/components/steps/currency-step.tsx +165 -0
- package/recipes/onboarding/apps/native/src/features/onboarding/expense-tracker/components/steps/feature-ai-step.tsx +199 -0
- package/recipes/onboarding/apps/native/src/features/onboarding/expense-tracker/components/steps/feature-chatbot-step.tsx +154 -0
- package/recipes/onboarding/apps/native/src/features/onboarding/expense-tracker/components/steps/feature-manual-step.tsx +156 -0
- package/recipes/onboarding/apps/native/src/features/onboarding/expense-tracker/components/steps/feature-scan-step.tsx +158 -0
- package/recipes/onboarding/apps/native/src/features/onboarding/expense-tracker/components/steps/main-reason-step.tsx +139 -0
- package/recipes/onboarding/apps/native/src/features/onboarding/expense-tracker/components/steps/notification-step.tsx +129 -0
- package/recipes/onboarding/apps/native/src/features/onboarding/expense-tracker/components/steps/overspend-step.tsx +138 -0
- package/recipes/onboarding/apps/native/src/features/onboarding/expense-tracker/components/steps/personalizing-step.tsx +190 -0
- package/recipes/onboarding/apps/native/src/features/onboarding/expense-tracker/components/steps/rating-step.tsx +98 -0
- package/recipes/onboarding/apps/native/src/features/onboarding/expense-tracker/components/steps/reminder-step.tsx +181 -0
- package/recipes/onboarding/apps/native/src/features/onboarding/expense-tracker/components/steps/safety-step.tsx +110 -0
- package/recipes/onboarding/apps/native/src/features/onboarding/expense-tracker/components/steps/struggle-step.tsx +139 -0
- package/recipes/onboarding/apps/native/src/features/onboarding/expense-tracker/components/steps/welcome-step.tsx +217 -0
- package/recipes/onboarding/apps/native/src/features/onboarding/expense-tracker/components/ui/onboarding-header.tsx +58 -0
- package/recipes/onboarding/apps/native/src/features/onboarding/expense-tracker/constants.ts +179 -0
- package/recipes/onboarding/apps/native/src/features/onboarding/hooks/use-onboarding-analytics.ts +323 -0
- package/recipes/onboarding/apps/native/src/features/onboarding/services/onboarding-analytics.ts +432 -0
- package/recipes/onboarding/recipe.json +15 -0
- package/recipes/onboarding@latest.zip +0 -0
- package/recipes/payments/recipe.json +28 -61
- package/recipes/payments-supabase/apps/native/src/features/payments/README.md +200 -0
- package/recipes/payments-supabase/apps/native/src/features/payments/app/local-paywall.tsx +194 -0
- package/recipes/payments-supabase/apps/native/src/features/payments/app/remote-paywall.tsx +79 -0
- package/recipes/payments-supabase/apps/native/src/features/payments/components/payment-initializer.tsx +95 -0
- package/recipes/payments-supabase/apps/native/src/features/payments/components/paywall-error-state.tsx +60 -0
- package/recipes/payments-supabase/apps/native/src/features/payments/components/paywall-local-mode.tsx +116 -0
- package/recipes/payments-supabase/apps/native/src/features/payments/components/paywall-product-card.tsx +133 -0
- package/recipes/payments-supabase/apps/native/src/features/payments/components/paywall-remote-mode.tsx +146 -0
- package/recipes/payments-supabase/apps/native/src/features/payments/hooks/use-entitlement.ts +63 -0
- package/recipes/payments-supabase/apps/native/src/features/payments/index.ts +8 -0
- package/recipes/payments-supabase/apps/native/src/features/payments/services/revenuecat-adapter.ts +407 -0
- package/recipes/payments-supabase/packages/backend/src/services/payments.ts +201 -0
- package/recipes/payments-supabase/packages/backend/supabase/migrations/payments.sql +35 -0
- package/recipes/payments-supabase/recipe.json +72 -0
- package/recipes/payments-supabase@latest.zip +0 -0
- package/recipes/payments@latest.zip +0 -0
- package/recipes/quiz/apps/native/src/features/quiz/index.tsx +1 -2
- package/recipes/quiz/recipe.json +6 -9
- package/recipes/quiz@latest.zip +0 -0
- package/recipes/tracker-app/apps/native/src/features/tracker-app/app/index.tsx +1 -2
- package/recipes/tracker-app/recipe.json +7 -10
- package/recipes/tracker-app@latest.zip +0 -0
- package/recipes/voice-bot/recipe.json +8 -68
- package/recipes/voice-bot.zip +0 -0
- package/recipes/voice-bot@latest.zip +0 -0
- package/recipes/wake-word/recipe.json +10 -9
- package/recipes/wake-word.zip +0 -0
- package/recipes/wake-word@latest.zip +0 -0
- package/recipes/charts/apps/native/src/app/(root)/(protected)/charts/index.tsx +0 -3
- package/recipes/chatbot/packages/backend/convex/lib/rateLimit.ts +0 -100
- package/recipes/chatbot/packages/backend/convex/lib/telemetry.ts +0 -29
- package/recipes/chatbot/packages/backend/convex/ragKnowledge.ts +0 -0
- package/recipes/image-analysis/apps/native/assets/features/image-analyzer/front.jpg +0 -0
- package/recipes/image-analysis/apps/native/assets/features/image-analyzer/side.jpg +0 -0
- package/recipes/image-analysis/apps/native/assets/features/image-analyzer/threeQuarter.jpg +0 -0
- package/recipes/image-analysis/apps/native/src/app/(root)/(protected)/analysis/[type]/_layout.tsx +0 -5
- package/recipes/image-analysis/apps/native/src/app/(root)/(protected)/analysis/[type]/analysis-options.tsx +0 -50
- package/recipes/image-analysis/apps/native/src/app/(root)/(protected)/analysis/[type]/camera.tsx +0 -2
- package/recipes/image-analysis/apps/native/src/app/(root)/(protected)/analysis/[type]/index.tsx +0 -50
- package/recipes/image-analysis/apps/native/src/app/(root)/(protected)/analysis/[type]/loading.tsx +0 -50
- package/recipes/image-analysis/apps/native/src/app/(root)/(protected)/analysis/[type]/results.tsx +0 -2
- package/recipes/image-analysis/apps/native/src/app/(root)/(protected)/analysis/[type]/trait-details.tsx +0 -3
- package/recipes/image-analysis/packages/backend/convex/imageAnalysisFunctions.ts +0 -325
- package/recipes/image-analysis/packages/backend/convex/lib/ai/imageAnalysisAdapter.ts +0 -200
- package/recipes/payments/apps/native/src/app/(root)/(protected)/paywall/index.tsx +0 -74
- package/recipes/payments/apps/native/src/app/(root)/(protected)/paywall/local.tsx +0 -25
- package/recipes/payments/apps/native/src/app/(root)/(protected)/paywall/remote.tsx +0 -23
- package/recipes/quiz/apps/native/src/app/(root)/(protected)/quiz/index.tsx +0 -47
- package/recipes/tracker-app/apps/native/src/app/(root)/(protected)/tracker-app/index.tsx +0 -1
- package/recipes/voice-bot/apps/native/src/app/(root)/(protected)/voice-bot/index.tsx +0 -27
- package/recipes/voice-bot/packages/backend/convex/router.ts +0 -81
- /package/recipes/{chatbot/apps/native/src/app/(root)/(protected) โ chatbot-supabase/apps/native/src/app}/chatbot/index.tsx +0 -0
- /package/recipes/{image-generator/apps/native/src/app/(root)/(protected) โ image-generator-supabase/apps/native/src/app}/image-generator/gallery.tsx +0 -0
- /package/recipes/{image-generator/apps/native/src/app/(root)/(protected) โ image-generator-supabase/apps/native/src/app}/image-generator/index.tsx +0 -0
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
import * as FileSystem from 'expo-file-system';
|
|
2
|
+
import * as ImageManipulator from 'expo-image-manipulator';
|
|
3
|
+
import { useCallback } from 'react';
|
|
4
|
+
import { create } from 'zustand';
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
useAnalyzeImages as useAnalyzeImageMutation,
|
|
8
|
+
useImageAnalysis as useImageAnalysisQuery,
|
|
9
|
+
useUserImageAnalyses,
|
|
10
|
+
} from '@/api-client/supabase/image-analyzer';
|
|
11
|
+
|
|
12
|
+
// Temporary store for camera capture data to avoid route parameter issues
|
|
13
|
+
type CameraCaptureStore = {
|
|
14
|
+
capturedImageUri: string | null;
|
|
15
|
+
stepId: string | null;
|
|
16
|
+
setCapturedImage: (uri: string, stepId: string) => void;
|
|
17
|
+
clearCapturedImage: () => void;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export const useCameraCaptureStore = create<CameraCaptureStore>((set) => ({
|
|
21
|
+
capturedImageUri: null,
|
|
22
|
+
stepId: null,
|
|
23
|
+
setCapturedImage: (uri: string, stepId: string) =>
|
|
24
|
+
set({ capturedImageUri: uri, stepId }),
|
|
25
|
+
clearCapturedImage: () => set({ capturedImageUri: null, stepId: null }),
|
|
26
|
+
}));
|
|
27
|
+
|
|
28
|
+
export type AnalyzeImagesParams = {
|
|
29
|
+
imageUris: string[];
|
|
30
|
+
preferences: {
|
|
31
|
+
goal: string;
|
|
32
|
+
feedbackStyle: string;
|
|
33
|
+
};
|
|
34
|
+
onProgress?: (step: string, progress: number) => void;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export type CreateImageAnalysisParams = {
|
|
38
|
+
imageUris: string[];
|
|
39
|
+
preferences: {
|
|
40
|
+
analysisGoal: string;
|
|
41
|
+
feedbackStyle: string;
|
|
42
|
+
};
|
|
43
|
+
onProgress?: (step: string, progress: number) => void;
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Hook for analyzing images using the Supabase Edge Function.
|
|
48
|
+
*/
|
|
49
|
+
export function useAnalyzeImages() {
|
|
50
|
+
const analyzeImageMutation = useAnalyzeImageMutation();
|
|
51
|
+
|
|
52
|
+
return useCallback(
|
|
53
|
+
async (params: AnalyzeImagesParams): Promise<string | null> => {
|
|
54
|
+
const { imageUris, preferences, onProgress } = params;
|
|
55
|
+
|
|
56
|
+
try {
|
|
57
|
+
onProgress?.('Optimizing images...', 15);
|
|
58
|
+
|
|
59
|
+
// Resize & compress images to reduce memory (max width 1024, quality 0.7)
|
|
60
|
+
const optimizedUris = await Promise.all(
|
|
61
|
+
imageUris.map(async (uri) => {
|
|
62
|
+
try {
|
|
63
|
+
const { uri: optimizedUri } =
|
|
64
|
+
await ImageManipulator.manipulateAsync(
|
|
65
|
+
uri,
|
|
66
|
+
[{ resize: { width: 1024 } }],
|
|
67
|
+
{
|
|
68
|
+
compress: 0.7,
|
|
69
|
+
format: ImageManipulator.SaveFormat.JPEG,
|
|
70
|
+
base64: false,
|
|
71
|
+
},
|
|
72
|
+
);
|
|
73
|
+
return optimizedUri;
|
|
74
|
+
} catch (err) {
|
|
75
|
+
console.warn('Image optimization failed, using original', err);
|
|
76
|
+
return uri;
|
|
77
|
+
}
|
|
78
|
+
}),
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
onProgress?.('Converting images...', 20);
|
|
82
|
+
|
|
83
|
+
// Convert optimized URIs to base64
|
|
84
|
+
const base64Images = await Promise.all(
|
|
85
|
+
optimizedUris.map((uri) =>
|
|
86
|
+
FileSystem.readAsStringAsync(uri, { encoding: 'base64' }),
|
|
87
|
+
),
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
onProgress?.('Starting analysis...', 40);
|
|
91
|
+
|
|
92
|
+
// Call the analyze image mutation
|
|
93
|
+
const result = await analyzeImageMutation.mutateAsync({
|
|
94
|
+
imagesAsBase64: base64Images,
|
|
95
|
+
preferences,
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
onProgress?.('Analysis in progress...', 50);
|
|
99
|
+
|
|
100
|
+
return result.analysisId;
|
|
101
|
+
} catch (error) {
|
|
102
|
+
console.error('Error starting image analysis:', error);
|
|
103
|
+
return null;
|
|
104
|
+
}
|
|
105
|
+
},
|
|
106
|
+
[analyzeImageMutation],
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Hook for creating and managing image analyses (legacy - for backward compatibility).
|
|
112
|
+
*/
|
|
113
|
+
export function useCreateImageAnalysis() {
|
|
114
|
+
const analyzeImageMutation = useAnalyzeImageMutation();
|
|
115
|
+
|
|
116
|
+
return useCallback(
|
|
117
|
+
async (params: CreateImageAnalysisParams): Promise<string | null> => {
|
|
118
|
+
const { imageUris, preferences, onProgress } = params;
|
|
119
|
+
|
|
120
|
+
try {
|
|
121
|
+
onProgress?.('Optimizing images...', 15);
|
|
122
|
+
|
|
123
|
+
// Resize & compress images to reduce memory (max width 1024, quality 0.7)
|
|
124
|
+
const optimizedUris = await Promise.all(
|
|
125
|
+
imageUris.map(async (uri) => {
|
|
126
|
+
try {
|
|
127
|
+
const { uri: optimizedUri } =
|
|
128
|
+
await ImageManipulator.manipulateAsync(
|
|
129
|
+
uri,
|
|
130
|
+
[{ resize: { width: 1024 } }],
|
|
131
|
+
{
|
|
132
|
+
compress: 0.7,
|
|
133
|
+
format: ImageManipulator.SaveFormat.JPEG,
|
|
134
|
+
base64: false,
|
|
135
|
+
},
|
|
136
|
+
);
|
|
137
|
+
return optimizedUri;
|
|
138
|
+
} catch (err) {
|
|
139
|
+
console.warn('Image optimization failed, using original', err);
|
|
140
|
+
return uri;
|
|
141
|
+
}
|
|
142
|
+
}),
|
|
143
|
+
);
|
|
144
|
+
|
|
145
|
+
onProgress?.('Converting images...', 20);
|
|
146
|
+
|
|
147
|
+
// Convert optimized URIs to base64
|
|
148
|
+
const base64Images = await Promise.all(
|
|
149
|
+
optimizedUris.map((uri) =>
|
|
150
|
+
FileSystem.readAsStringAsync(uri, { encoding: 'base64' }),
|
|
151
|
+
),
|
|
152
|
+
);
|
|
153
|
+
|
|
154
|
+
onProgress?.('Starting analysis...', 40);
|
|
155
|
+
|
|
156
|
+
// Create the analysis with uploaded images
|
|
157
|
+
const result = await analyzeImageMutation.mutateAsync({
|
|
158
|
+
imagesAsBase64: base64Images,
|
|
159
|
+
preferences: {
|
|
160
|
+
goal: preferences.analysisGoal,
|
|
161
|
+
feedbackStyle: preferences.feedbackStyle,
|
|
162
|
+
},
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
onProgress?.('Analysis in progress...', 50);
|
|
166
|
+
|
|
167
|
+
return result.analysisId;
|
|
168
|
+
} catch (error) {
|
|
169
|
+
console.error('Error creating image analysis:', error);
|
|
170
|
+
return null;
|
|
171
|
+
}
|
|
172
|
+
},
|
|
173
|
+
[analyzeImageMutation],
|
|
174
|
+
);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Hook for polling analysis status by ID.
|
|
179
|
+
*/
|
|
180
|
+
export function useAnalysisStatus(analysisId?: string) {
|
|
181
|
+
const { data } = useImageAnalysisQuery(analysisId ?? null);
|
|
182
|
+
return data;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Hook for fetching an image analysis by ID.
|
|
187
|
+
*/
|
|
188
|
+
export function useImageAnalysis(analysisId?: string) {
|
|
189
|
+
const { data } = useImageAnalysisQuery(analysisId ?? null);
|
|
190
|
+
return data;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Hook for fetching user's image analysis history with optional pagination.
|
|
195
|
+
*/
|
|
196
|
+
export function useImageAnalysisHistory(paginationOpts?: {
|
|
197
|
+
limit: number;
|
|
198
|
+
offset: number;
|
|
199
|
+
}) {
|
|
200
|
+
const { data } = useUserImageAnalyses({ limit: paginationOpts?.limit ?? 50 });
|
|
201
|
+
const pages = data?.pages ?? [];
|
|
202
|
+
const allAnalyses = pages.flatMap((page) => page?.analyses ?? []);
|
|
203
|
+
return {
|
|
204
|
+
page: allAnalyses,
|
|
205
|
+
isDone: allAnalyses.length < (paginationOpts?.limit ?? 50),
|
|
206
|
+
continueCursor: null,
|
|
207
|
+
};
|
|
208
|
+
}
|
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
export type AnalysisScores = {
|
|
2
|
+
overall: number;
|
|
3
|
+
[key: string]: number;
|
|
4
|
+
};
|
|
5
|
+
|
|
6
|
+
export type TraitAnalysis = {
|
|
7
|
+
score: number;
|
|
8
|
+
feedback: string;
|
|
9
|
+
strengths?: string[];
|
|
10
|
+
improvements?: string[];
|
|
11
|
+
// Legacy support for old format
|
|
12
|
+
Score?: number;
|
|
13
|
+
Feedback?: string;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
// Generic trait scores - can work with any analysis type
|
|
17
|
+
export type GenericTraitScores = Record<string, TraitAnalysis>;
|
|
18
|
+
|
|
19
|
+
export type LocalAnalysis = {
|
|
20
|
+
id: string;
|
|
21
|
+
localImageUri?: string;
|
|
22
|
+
aiResults?: {
|
|
23
|
+
overallScore?: number;
|
|
24
|
+
overallMessage?: string;
|
|
25
|
+
confidence?: number;
|
|
26
|
+
traitScores?: GenericTraitScores; // Now generic instead of hard-coded
|
|
27
|
+
recommendations?: string[];
|
|
28
|
+
analysisType?: string;
|
|
29
|
+
processingTime?: number;
|
|
30
|
+
// Legacy support for old format
|
|
31
|
+
analysis?: string; // Legacy field name for overallMessage
|
|
32
|
+
[key: string]: any;
|
|
33
|
+
};
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Extract scores from any analysis type (completely dynamic)
|
|
38
|
+
*/
|
|
39
|
+
export function extractScoresFromAnalysis(
|
|
40
|
+
analysis: LocalAnalysis['aiResults'],
|
|
41
|
+
): AnalysisScores | null {
|
|
42
|
+
if (!analysis) return null;
|
|
43
|
+
|
|
44
|
+
const scores: AnalysisScores = {
|
|
45
|
+
overall: analysis.overallScore || 0,
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
// Handle structured trait scores (new format)
|
|
49
|
+
if (analysis.traitScores && typeof analysis.traitScores === 'object') {
|
|
50
|
+
Object.entries(analysis.traitScores).forEach(([key, trait]) => {
|
|
51
|
+
if (trait && typeof trait === 'object') {
|
|
52
|
+
// Support both new format (score) and legacy format (Score)
|
|
53
|
+
const score = (trait as TraitAnalysis).score || (trait as any).Score;
|
|
54
|
+
if (typeof score === 'number') {
|
|
55
|
+
scores[key] = score;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Handle legacy flat format (for backward compatibility)
|
|
62
|
+
Object.entries(analysis).forEach(([key, value]) => {
|
|
63
|
+
if (
|
|
64
|
+
key !== 'overallScore' &&
|
|
65
|
+
key !== 'overallMessage' &&
|
|
66
|
+
key !== 'confidence' &&
|
|
67
|
+
key !== 'traitScores' &&
|
|
68
|
+
key !== 'recommendations' &&
|
|
69
|
+
key !== 'analysisType' &&
|
|
70
|
+
key !== 'processingTime'
|
|
71
|
+
) {
|
|
72
|
+
if (value && typeof value === 'object' && 'Score' in value) {
|
|
73
|
+
scores[key] = (value as any).Score;
|
|
74
|
+
} else if (typeof value === 'number') {
|
|
75
|
+
scores[key] = value;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
return scores;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Process image URL from parameters with priority for multiple images
|
|
85
|
+
*/
|
|
86
|
+
export function processImageUrl(
|
|
87
|
+
localImageUriParam: string | null,
|
|
88
|
+
localImageUrisParam: string | null,
|
|
89
|
+
fallbackImageUri: string | null,
|
|
90
|
+
): string | null {
|
|
91
|
+
// Priority 1: Multiple images from localImageUris parameter
|
|
92
|
+
if (localImageUrisParam) {
|
|
93
|
+
try {
|
|
94
|
+
const parsedUris = JSON.parse(localImageUrisParam);
|
|
95
|
+
if (Array.isArray(parsedUris) && parsedUris.length > 0) {
|
|
96
|
+
return parsedUris[0]; // Return first image
|
|
97
|
+
}
|
|
98
|
+
} catch (error) {
|
|
99
|
+
console.warn('Failed to parse localImageUris:', error);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Priority 2: Single image from localImageUri parameter
|
|
104
|
+
if (localImageUriParam) {
|
|
105
|
+
return localImageUriParam;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Priority 3: Fallback image URI
|
|
109
|
+
return fallbackImageUri;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Format trait name for display (completely generic)
|
|
114
|
+
*/
|
|
115
|
+
export function formatTraitName(traitKey: string): string {
|
|
116
|
+
// Convert camelCase to Title Case
|
|
117
|
+
return traitKey
|
|
118
|
+
.replace(/([A-Z])/g, ' $1')
|
|
119
|
+
.replace(/^./, (str) => str.toUpperCase())
|
|
120
|
+
.trim();
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Get trait details for any analysis type (dynamic)
|
|
125
|
+
*/
|
|
126
|
+
export function getTraitDetails(
|
|
127
|
+
traitKey: string,
|
|
128
|
+
traitScores: GenericTraitScores | undefined,
|
|
129
|
+
): {
|
|
130
|
+
score: number;
|
|
131
|
+
feedback: string;
|
|
132
|
+
strengths: string[];
|
|
133
|
+
improvements: string[];
|
|
134
|
+
} | null {
|
|
135
|
+
if (!traitScores || !traitScores[traitKey]) {
|
|
136
|
+
return null;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const trait = traitScores[traitKey];
|
|
140
|
+
|
|
141
|
+
return {
|
|
142
|
+
score: trait.score || trait.Score || 0,
|
|
143
|
+
feedback: trait.feedback || trait.Feedback || 'No feedback available',
|
|
144
|
+
strengths: trait.strengths || [],
|
|
145
|
+
improvements: trait.improvements || [],
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Generic achievement system that works with any analysis type
|
|
151
|
+
*/
|
|
152
|
+
export type Achievement = {
|
|
153
|
+
id: string;
|
|
154
|
+
title: string;
|
|
155
|
+
description: string;
|
|
156
|
+
icon: string;
|
|
157
|
+
color: string;
|
|
158
|
+
points: number;
|
|
159
|
+
condition: (scores: AnalysisScores) => boolean;
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
const GENERIC_ACHIEVEMENTS: Achievement[] = [
|
|
163
|
+
{
|
|
164
|
+
id: 'excellentScore',
|
|
165
|
+
title: 'Excellence',
|
|
166
|
+
description: 'Achieved an excellent overall score',
|
|
167
|
+
icon: 'trophy-outline',
|
|
168
|
+
color: '#FFD700',
|
|
169
|
+
points: 50,
|
|
170
|
+
condition: (scores) => scores.overall >= 90,
|
|
171
|
+
},
|
|
172
|
+
{
|
|
173
|
+
id: 'goodAnalysis',
|
|
174
|
+
title: 'Good Analysis',
|
|
175
|
+
description: 'Scored well in the analysis',
|
|
176
|
+
icon: 'thumbs-up-outline',
|
|
177
|
+
color: '#4CAF50',
|
|
178
|
+
points: 30,
|
|
179
|
+
condition: (scores) => scores.overall >= 75,
|
|
180
|
+
},
|
|
181
|
+
{
|
|
182
|
+
id: 'balanced',
|
|
183
|
+
title: 'Well Balanced',
|
|
184
|
+
description: 'Consistent scores across all traits',
|
|
185
|
+
icon: 'scale-outline',
|
|
186
|
+
color: '#2196F3',
|
|
187
|
+
points: 25,
|
|
188
|
+
condition: (scores) => {
|
|
189
|
+
const traitScores = Object.entries(scores)
|
|
190
|
+
.filter(([key]) => key !== 'overall')
|
|
191
|
+
.map(([, score]) => score);
|
|
192
|
+
if (traitScores.length < 2) return false;
|
|
193
|
+
const avg = traitScores.reduce((a, b) => a + b, 0) / traitScores.length;
|
|
194
|
+
const variance =
|
|
195
|
+
traitScores.reduce((acc, score) => acc + (score - avg) ** 2, 0) /
|
|
196
|
+
traitScores.length;
|
|
197
|
+
return variance < 100; // Low variance means balanced
|
|
198
|
+
},
|
|
199
|
+
},
|
|
200
|
+
{
|
|
201
|
+
id: 'highConfidence',
|
|
202
|
+
title: 'Clear Assessment',
|
|
203
|
+
description: 'Analysis completed with high confidence',
|
|
204
|
+
icon: 'eye-outline',
|
|
205
|
+
color: '#9C27B0',
|
|
206
|
+
points: 20,
|
|
207
|
+
condition: () => true, // We don't have confidence in scores, so always true
|
|
208
|
+
},
|
|
209
|
+
];
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Get unlocked achievements based on analysis scores
|
|
213
|
+
*/
|
|
214
|
+
export function getUnlockedAchievements(scores: AnalysisScores): Achievement[] {
|
|
215
|
+
return GENERIC_ACHIEVEMENTS.filter((achievement) =>
|
|
216
|
+
achievement.condition(scores),
|
|
217
|
+
);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Creates a mock analysis for fallback (generic)
|
|
222
|
+
*/
|
|
223
|
+
export function createMockAnalysis(analysisId: string): LocalAnalysis {
|
|
224
|
+
return {
|
|
225
|
+
id: analysisId,
|
|
226
|
+
localImageUri:
|
|
227
|
+
'https://images.unsplash.com/photo-1579933113359-f65a43033118?w=800&q=80',
|
|
228
|
+
aiResults: {
|
|
229
|
+
overallScore: 85,
|
|
230
|
+
overallMessage:
|
|
231
|
+
'Great analysis results with strong performance across multiple areas.',
|
|
232
|
+
confidence: 0.9,
|
|
233
|
+
traitScores: {
|
|
234
|
+
primaryTrait: {
|
|
235
|
+
score: 90,
|
|
236
|
+
feedback: 'Excellent primary characteristic',
|
|
237
|
+
strengths: ['Strong foundation', 'Good structure'],
|
|
238
|
+
improvements: ['Minor refinements possible'],
|
|
239
|
+
},
|
|
240
|
+
secondaryTrait: {
|
|
241
|
+
score: 80,
|
|
242
|
+
feedback: 'Very good secondary aspect',
|
|
243
|
+
strengths: ['Well-balanced', 'Consistent quality'],
|
|
244
|
+
improvements: ['Some enhancement opportunities'],
|
|
245
|
+
},
|
|
246
|
+
tertiaryTrait: {
|
|
247
|
+
score: 85,
|
|
248
|
+
feedback: 'Strong tertiary element',
|
|
249
|
+
strengths: ['Natural appeal', 'Good proportions'],
|
|
250
|
+
improvements: ['Fine-tuning recommended'],
|
|
251
|
+
},
|
|
252
|
+
},
|
|
253
|
+
recommendations: [
|
|
254
|
+
'Continue maintaining current standards',
|
|
255
|
+
'Focus on areas with the most potential',
|
|
256
|
+
'Consider professional guidance for optimization',
|
|
257
|
+
],
|
|
258
|
+
analysisType: 'generic_analysis',
|
|
259
|
+
processingTime: 2500,
|
|
260
|
+
},
|
|
261
|
+
};
|
|
262
|
+
}
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
import * as FileSystem from 'expo-file-system';
|
|
2
|
+
import { Alert, Platform, Share } from 'react-native';
|
|
3
|
+
|
|
4
|
+
import type { AnalysisScores, LocalAnalysis } from './analysis-service';
|
|
5
|
+
import { formatTraitName } from './analysis-service';
|
|
6
|
+
|
|
7
|
+
export type ShareableAnalysisData = {
|
|
8
|
+
overallScore: number;
|
|
9
|
+
traitScores: AnalysisScores;
|
|
10
|
+
imageUrl?: string | null;
|
|
11
|
+
analysisType?: string;
|
|
12
|
+
recommendations?: string[];
|
|
13
|
+
overallMessage?: string;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Service for sharing analysis results
|
|
18
|
+
*/
|
|
19
|
+
export class ShareService {
|
|
20
|
+
/**
|
|
21
|
+
* Share analysis results with formatted text
|
|
22
|
+
*/
|
|
23
|
+
static async shareAnalysisResults(
|
|
24
|
+
analysis: LocalAnalysis,
|
|
25
|
+
extractedScores: AnalysisScores,
|
|
26
|
+
imageUrl?: string | null,
|
|
27
|
+
): Promise<void> {
|
|
28
|
+
try {
|
|
29
|
+
const shareData = await ShareService.prepareShareData({
|
|
30
|
+
overallScore: extractedScores.overall,
|
|
31
|
+
traitScores: extractedScores,
|
|
32
|
+
imageUrl,
|
|
33
|
+
analysisType: analysis.aiResults?.analysisType,
|
|
34
|
+
recommendations: analysis.aiResults?.recommendations,
|
|
35
|
+
overallMessage:
|
|
36
|
+
analysis.aiResults?.overallMessage || analysis.aiResults?.analysis, // Handle legacy field name
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
const result = await Share.share(shareData, {
|
|
40
|
+
dialogTitle: 'Share Analysis Results',
|
|
41
|
+
excludedActivityTypes: Platform.select({
|
|
42
|
+
ios: [
|
|
43
|
+
'com.apple.UIKit.activity.AssignToContact',
|
|
44
|
+
'com.apple.UIKit.activity.Print',
|
|
45
|
+
],
|
|
46
|
+
default: undefined,
|
|
47
|
+
}),
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
if (result.action === Share.sharedAction) {
|
|
51
|
+
console.log('Analysis results shared successfully');
|
|
52
|
+
}
|
|
53
|
+
} catch (error) {
|
|
54
|
+
console.error('Error sharing analysis results:', error);
|
|
55
|
+
Alert.alert('Share Failed', 'Unable to share results. Please try again.');
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Prepare shareable data with formatted text
|
|
61
|
+
*/
|
|
62
|
+
private static async prepareShareData(data: ShareableAnalysisData): Promise<{
|
|
63
|
+
message: string;
|
|
64
|
+
url?: string;
|
|
65
|
+
}> {
|
|
66
|
+
const formattedText = ShareService.formatAnalysisText(data);
|
|
67
|
+
|
|
68
|
+
const shareData: { message: string; url?: string } = {
|
|
69
|
+
message: formattedText,
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
// Add image if available (for platforms that support it)
|
|
73
|
+
if (data.imageUrl && Platform.OS !== 'web') {
|
|
74
|
+
try {
|
|
75
|
+
// Verify the image is accessible
|
|
76
|
+
const fileInfo = await FileSystem.getInfoAsync(data.imageUrl);
|
|
77
|
+
if (fileInfo.exists) {
|
|
78
|
+
shareData.url = data.imageUrl;
|
|
79
|
+
}
|
|
80
|
+
} catch (error) {
|
|
81
|
+
console.warn('Could not attach image to share:', error);
|
|
82
|
+
// Continue without image
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return shareData;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Format analysis results into readable text
|
|
91
|
+
*/
|
|
92
|
+
private static formatAnalysisText(data: ShareableAnalysisData): string {
|
|
93
|
+
const { overallScore, traitScores, analysisType, recommendations } = data;
|
|
94
|
+
|
|
95
|
+
let text = '๐ My Analysis Results\n\n';
|
|
96
|
+
|
|
97
|
+
// Overall score
|
|
98
|
+
text += `Overall Score: ${overallScore}/100\n`;
|
|
99
|
+
text += `${ShareService.getScoreLevelEmoji(overallScore)} ${ShareService.getScoreLevel(overallScore)}\n\n`;
|
|
100
|
+
|
|
101
|
+
// Overall message/feedback (if available)
|
|
102
|
+
if (data.overallMessage) {
|
|
103
|
+
text += `๐ฌ Overall Assessment:\n${data.overallMessage}\n\n`;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Individual trait scores
|
|
107
|
+
text += '๐ Trait Breakdown:\n';
|
|
108
|
+
Object.entries(traitScores)
|
|
109
|
+
.filter(([key]) => key !== 'overall')
|
|
110
|
+
.sort(([, a], [, b]) => b - a) // Sort by score descending
|
|
111
|
+
.forEach(([key, score]) => {
|
|
112
|
+
const formattedName = formatTraitName(key);
|
|
113
|
+
text += `โข ${formattedName}: ${score}/100 ${ShareService.getScoreLevelEmoji(score)}\n`;
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
// Recommendations (if available)
|
|
117
|
+
if (recommendations && recommendations.length > 0) {
|
|
118
|
+
text += '\n๐ก Key Recommendations:\n';
|
|
119
|
+
recommendations.slice(0, 3).forEach((rec, index) => {
|
|
120
|
+
text += `${index + 1}. ${rec}\n`;
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Analysis type context
|
|
125
|
+
if (analysisType) {
|
|
126
|
+
text += `\nAnalysis Type: ${analysisType}\n`;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
text += '\nโจ Get your own analysis with ShipAppsFast!';
|
|
130
|
+
|
|
131
|
+
return text;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Get score level description
|
|
136
|
+
*/
|
|
137
|
+
private static getScoreLevel(score: number): string {
|
|
138
|
+
if (score >= 80) return 'Excellent';
|
|
139
|
+
if (score >= 70) return 'Very Good';
|
|
140
|
+
if (score >= 60) return 'Good';
|
|
141
|
+
if (score >= 50) return 'Fair';
|
|
142
|
+
return 'Needs Improvement';
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Get appropriate emoji for score level
|
|
147
|
+
*/
|
|
148
|
+
private static getScoreLevelEmoji(score: number): string {
|
|
149
|
+
if (score >= 80) return '๐';
|
|
150
|
+
if (score >= 70) return '๐';
|
|
151
|
+
if (score >= 60) return '๐';
|
|
152
|
+
if (score >= 50) return '๐งก';
|
|
153
|
+
return '๐';
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Share analysis results as a quick summary
|
|
158
|
+
*/
|
|
159
|
+
static async shareQuickSummary(
|
|
160
|
+
overallScore: number,
|
|
161
|
+
topTrait: { name: string; score: number },
|
|
162
|
+
): Promise<void> {
|
|
163
|
+
const text =
|
|
164
|
+
'๐ Just completed my analysis!\n\n' +
|
|
165
|
+
`Overall Score: ${overallScore}/100 ${ShareService.getScoreLevelEmoji(overallScore)}\n` +
|
|
166
|
+
`Top Trait: ${topTrait.name} (${topTrait.score}/100)\n\n` +
|
|
167
|
+
'โจ Get your own analysis with ShipAppsFast!';
|
|
168
|
+
|
|
169
|
+
try {
|
|
170
|
+
await Share.share({ message: text });
|
|
171
|
+
} catch (error) {
|
|
172
|
+
console.error('Error sharing quick summary:', error);
|
|
173
|
+
Alert.alert('Share Failed', 'Unable to share. Please try again.');
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|