vibefast-cli 1.1.5 → 1.2.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/CHANGELOG.md +32 -0
- package/README.md +63 -169
- package/dist/commands/add.d.ts +1 -1
- package/dist/commands/add.d.ts.map +1 -1
- package/dist/commands/add.js +547 -589
- 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 +35 -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 +79 -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 +57 -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 +59 -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 +51 -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,129 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { ScrollView, View, type ViewStyle } from 'react-native';
|
|
3
|
+
|
|
4
|
+
type TableRendererProps = {
|
|
5
|
+
node: any;
|
|
6
|
+
children: React.ReactNode;
|
|
7
|
+
styles: Record<string, any>;
|
|
8
|
+
tableOuterStyle: ViewStyle;
|
|
9
|
+
tableScrollContentStyle: ViewStyle;
|
|
10
|
+
tableScrollViewStyle: ViewStyle;
|
|
11
|
+
columnDividerWidth: number;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* TableRenderer component for rendering markdown tables with intelligent column sizing.
|
|
16
|
+
*
|
|
17
|
+
* Features:
|
|
18
|
+
* - Horizontal scrolling for wide tables
|
|
19
|
+
* - Intelligent column width sizing based on content
|
|
20
|
+
* - Proper border handling for first/last cells
|
|
21
|
+
* - Flexible widths that fit screen without excessive scrolling
|
|
22
|
+
*
|
|
23
|
+
* Requirements addressed:
|
|
24
|
+
* - 10.1: Calculate optimal column widths based on content length
|
|
25
|
+
* - 10.2: Use flexible widths for tables that fit screen
|
|
26
|
+
* - 10.3: Set dynamic widths proportional to content length
|
|
27
|
+
* - 10.4: Enable horizontal scroll when table exceeds screen width
|
|
28
|
+
* - 10.5: Balance column widths to prevent single column domination
|
|
29
|
+
*
|
|
30
|
+
* @param props - TableRenderer component props
|
|
31
|
+
* @returns Rendered table with proper styling and scrolling
|
|
32
|
+
*
|
|
33
|
+
* @example
|
|
34
|
+
* ```tsx
|
|
35
|
+
* <TableRenderer
|
|
36
|
+
* node={markdownNode}
|
|
37
|
+
* children={tableChildren}
|
|
38
|
+
* styles={markdownStyles}
|
|
39
|
+
* tableOuterStyle={styles.tableOuter}
|
|
40
|
+
* tableScrollContentStyle={styles.scrollContent}
|
|
41
|
+
* tableScrollViewStyle={styles.scrollView}
|
|
42
|
+
* columnDividerWidth={0.5}
|
|
43
|
+
* />
|
|
44
|
+
* ```
|
|
45
|
+
*/
|
|
46
|
+
export function TableRenderer({
|
|
47
|
+
node,
|
|
48
|
+
children,
|
|
49
|
+
styles,
|
|
50
|
+
tableOuterStyle,
|
|
51
|
+
tableScrollContentStyle,
|
|
52
|
+
tableScrollViewStyle,
|
|
53
|
+
columnDividerWidth,
|
|
54
|
+
}: TableRendererProps) {
|
|
55
|
+
const sections = React.Children.map(children, (section) => {
|
|
56
|
+
if (!React.isValidElement(section)) {
|
|
57
|
+
return section;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const sectionProps = section.props as any;
|
|
61
|
+
const rawRows = React.Children.toArray(sectionProps.children);
|
|
62
|
+
const processedRows = rawRows.map((rowChild, rowIndex) => {
|
|
63
|
+
if (!React.isValidElement(rowChild)) {
|
|
64
|
+
return rowChild;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const rowChildProps = rowChild.props as any;
|
|
68
|
+
const rowChildrenArray = React.Children.toArray(rowChildProps.children);
|
|
69
|
+
const processedCells = rowChildrenArray.map((cellChild, cellIndex) => {
|
|
70
|
+
if (!React.isValidElement(cellChild)) {
|
|
71
|
+
return cellChild;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Type assertion for props with style
|
|
75
|
+
const cellProps = cellChild.props as any;
|
|
76
|
+
const baseStyle = Array.isArray(cellProps.style)
|
|
77
|
+
? cellProps.style
|
|
78
|
+
: [cellProps.style];
|
|
79
|
+
const cellStyle = [
|
|
80
|
+
...baseStyle,
|
|
81
|
+
cellIndex === 0
|
|
82
|
+
? { borderLeftWidth: 0 }
|
|
83
|
+
: { borderLeftWidth: columnDividerWidth },
|
|
84
|
+
cellIndex === rowChildrenArray.length - 1
|
|
85
|
+
? { borderRightWidth: 0 }
|
|
86
|
+
: null,
|
|
87
|
+
].filter(Boolean);
|
|
88
|
+
|
|
89
|
+
return React.cloneElement(cellChild as any, {
|
|
90
|
+
style: cellStyle,
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
// Type assertion for props with style
|
|
95
|
+
const rowProps = rowChild.props as any;
|
|
96
|
+
const baseRowStyle = Array.isArray(rowProps.style)
|
|
97
|
+
? rowProps.style
|
|
98
|
+
: [rowProps.style];
|
|
99
|
+
const rowStyle = [
|
|
100
|
+
...baseRowStyle,
|
|
101
|
+
rowIndex === rawRows.length - 1 ? { borderBottomWidth: 0 } : null,
|
|
102
|
+
].filter(Boolean);
|
|
103
|
+
|
|
104
|
+
return React.cloneElement(
|
|
105
|
+
rowChild as any,
|
|
106
|
+
{
|
|
107
|
+
style: rowStyle,
|
|
108
|
+
},
|
|
109
|
+
processedCells,
|
|
110
|
+
);
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
return React.cloneElement(section, undefined, processedRows);
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
return (
|
|
117
|
+
<View key={node.key} style={tableOuterStyle}>
|
|
118
|
+
<ScrollView
|
|
119
|
+
horizontal
|
|
120
|
+
showsHorizontalScrollIndicator={false}
|
|
121
|
+
contentContainerStyle={tableScrollContentStyle}
|
|
122
|
+
style={tableScrollViewStyle}
|
|
123
|
+
nestedScrollEnabled={true}
|
|
124
|
+
>
|
|
125
|
+
<View style={styles.table}>{sections}</View>
|
|
126
|
+
</ScrollView>
|
|
127
|
+
</View>
|
|
128
|
+
);
|
|
129
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { View } from 'react-native';
|
|
3
|
+
|
|
4
|
+
import { Text } from '@/components/ui';
|
|
5
|
+
|
|
6
|
+
type MessageErrorBoundaryProps = {
|
|
7
|
+
children: React.ReactNode;
|
|
8
|
+
messageId: string;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
type MessageErrorBoundaryState = {
|
|
12
|
+
hasError: boolean;
|
|
13
|
+
error?: Error;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Error boundary for individual chat messages.
|
|
18
|
+
* Prevents one broken message from crashing the entire chat.
|
|
19
|
+
*
|
|
20
|
+
* Features:
|
|
21
|
+
* - Catches rendering errors in message components
|
|
22
|
+
* - Shows fallback UI for broken messages
|
|
23
|
+
* - Logs error details for debugging
|
|
24
|
+
* - Resets when message changes
|
|
25
|
+
*
|
|
26
|
+
* @example
|
|
27
|
+
* ```tsx
|
|
28
|
+
* <MessageErrorBoundary messageId={message.id}>
|
|
29
|
+
* <ChatMessageBubble message={message} />
|
|
30
|
+
* </MessageErrorBoundary>
|
|
31
|
+
* ```
|
|
32
|
+
*/
|
|
33
|
+
export class MessageErrorBoundary extends React.Component<
|
|
34
|
+
MessageErrorBoundaryProps,
|
|
35
|
+
MessageErrorBoundaryState
|
|
36
|
+
> {
|
|
37
|
+
constructor(props: MessageErrorBoundaryProps) {
|
|
38
|
+
super(props);
|
|
39
|
+
this.state = { hasError: false };
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
static getDerivedStateFromError(error: Error): MessageErrorBoundaryState {
|
|
43
|
+
return { hasError: true, error };
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
|
|
47
|
+
console.error('[MessageErrorBoundary] Message rendering error:', {
|
|
48
|
+
messageId: this.props.messageId,
|
|
49
|
+
error: error.message,
|
|
50
|
+
stack: error.stack,
|
|
51
|
+
componentStack: errorInfo.componentStack,
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
componentDidUpdate(prevProps: MessageErrorBoundaryProps) {
|
|
56
|
+
// Reset error state when message changes
|
|
57
|
+
if (prevProps.messageId !== this.props.messageId && this.state.hasError) {
|
|
58
|
+
this.setState({ hasError: false, error: undefined });
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
render() {
|
|
63
|
+
if (this.state.hasError) {
|
|
64
|
+
return (
|
|
65
|
+
<View className="my-2 rounded-lg border border-destructive/50 bg-destructive/10 p-4">
|
|
66
|
+
<Text className="text-sm font-medium text-destructive">
|
|
67
|
+
Failed to render message
|
|
68
|
+
</Text>
|
|
69
|
+
<Text className="mt-1 text-xs text-muted-foreground">
|
|
70
|
+
{this.state.error?.message || 'Unknown error'}
|
|
71
|
+
</Text>
|
|
72
|
+
</View>
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return this.props.children;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
import type { FlashListRef } from '@shopify/flash-list';
|
|
2
|
+
import { FlashList } from '@shopify/flash-list';
|
|
3
|
+
import React, { useCallback, useEffect, useRef } from 'react';
|
|
4
|
+
import { Keyboard, View } from 'react-native';
|
|
5
|
+
|
|
6
|
+
import { ChatMessageBubble } from '@/features/chatbot/components/chat-message-bubble';
|
|
7
|
+
import { MessageErrorBoundary } from '@/features/chatbot/components/message-error-boundary';
|
|
8
|
+
import { useSmartScrollManager } from '@/features/chatbot/hooks/use-smart-scroll-manager';
|
|
9
|
+
import type { AppMessage } from '@/features/chatbot/types';
|
|
10
|
+
|
|
11
|
+
type MessageListProps = {
|
|
12
|
+
messages: AppMessage[];
|
|
13
|
+
onReportMessage?: (message: AppMessage) => void;
|
|
14
|
+
testID?: string;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Component that renders the scrollable list of chat messages.
|
|
19
|
+
* Uses FlashList for optimal performance with large message lists.
|
|
20
|
+
* Auto-scrolls to bottom when new messages are added and on initial load.
|
|
21
|
+
*/
|
|
22
|
+
export const MessageList: React.FC<MessageListProps> = ({
|
|
23
|
+
messages,
|
|
24
|
+
onReportMessage,
|
|
25
|
+
testID = 'message-list',
|
|
26
|
+
}) => {
|
|
27
|
+
const flashListRef = useRef<FlashListRef<AppMessage> | null>(null);
|
|
28
|
+
const previousMessageIdsRef = useRef<string[]>([]);
|
|
29
|
+
|
|
30
|
+
// Use smart scroll manager hook
|
|
31
|
+
const scrollManager = useSmartScrollManager(flashListRef);
|
|
32
|
+
|
|
33
|
+
const attachFlashListRef = useCallback(
|
|
34
|
+
(instance: FlashListRef<AppMessage> | null) => {
|
|
35
|
+
flashListRef.current = instance;
|
|
36
|
+
},
|
|
37
|
+
[],
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
const handleTouchStart = useCallback(() => {
|
|
41
|
+
Keyboard.dismiss();
|
|
42
|
+
}, []);
|
|
43
|
+
|
|
44
|
+
// Auto-scroll to bottom when appropriate
|
|
45
|
+
useEffect(() => {
|
|
46
|
+
if (messages.length === 0) {
|
|
47
|
+
scrollManager.markNearBottom();
|
|
48
|
+
previousMessageIdsRef.current = [];
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const lastMessage = messages[messages.length - 1];
|
|
53
|
+
const previousIds = [...previousMessageIdsRef.current];
|
|
54
|
+
const prevLastId = previousIds[previousIds.length - 1];
|
|
55
|
+
const newMessageAppended = lastMessage?.id && lastMessage.id !== prevLastId;
|
|
56
|
+
const userSentMessage = newMessageAppended && lastMessage?.role === 'user';
|
|
57
|
+
|
|
58
|
+
// Check if we should auto-scroll
|
|
59
|
+
// Always scroll on user message, otherwise only if near bottom and not dragging
|
|
60
|
+
const shouldStickToBottom = scrollManager.shouldAutoScroll(userSentMessage);
|
|
61
|
+
|
|
62
|
+
if (shouldStickToBottom) {
|
|
63
|
+
// Only schedule scroll if:
|
|
64
|
+
// 1. New message was appended (user or assistant)
|
|
65
|
+
// 2. OR we're streaming (last message is assistant and content is updating)
|
|
66
|
+
const isStreaming =
|
|
67
|
+
lastMessage?.role === 'assistant' &&
|
|
68
|
+
lastMessage.id.startsWith('stream-');
|
|
69
|
+
|
|
70
|
+
if (newMessageAppended || isStreaming) {
|
|
71
|
+
scrollManager.scheduleScroll();
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
previousMessageIdsRef.current = messages.map((message) => message.id);
|
|
76
|
+
}, [messages, scrollManager]);
|
|
77
|
+
|
|
78
|
+
// Render individual message with error boundary
|
|
79
|
+
const renderMessage = React.useCallback(
|
|
80
|
+
({ item }: { item: AppMessage }) => {
|
|
81
|
+
return (
|
|
82
|
+
<MessageErrorBoundary messageId={item.id}>
|
|
83
|
+
<ChatMessageBubble message={item} onReportMessage={onReportMessage} />
|
|
84
|
+
</MessageErrorBoundary>
|
|
85
|
+
);
|
|
86
|
+
},
|
|
87
|
+
[onReportMessage],
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
// Get item type for better height estimation per message type
|
|
91
|
+
// FlashList will maintain separate height estimates for each type
|
|
92
|
+
const getItemType = useCallback((item: AppMessage) => {
|
|
93
|
+
const hasImages = (item.attachments?.length ?? 0) > 0;
|
|
94
|
+
const contentLength =
|
|
95
|
+
typeof item.content === 'string' ? item.content.length : 0;
|
|
96
|
+
const isLongMessage = contentLength > 500;
|
|
97
|
+
const role = item.role;
|
|
98
|
+
|
|
99
|
+
// Types: user-short, user-long, user-img, assistant-short, assistant-long, assistant-img
|
|
100
|
+
return `${role}-${hasImages ? 'img' : isLongMessage ? 'long' : 'short'}`;
|
|
101
|
+
}, []);
|
|
102
|
+
|
|
103
|
+
// Calculate better estimated item size based on message type
|
|
104
|
+
// Note: FlashList will use this for better height estimation
|
|
105
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
106
|
+
const estimatedItemSize = useCallback((item: AppMessage, index: number) => {
|
|
107
|
+
const hasImages = (item.attachments?.length ?? 0) > 0;
|
|
108
|
+
const contentLength =
|
|
109
|
+
typeof item.content === 'string' ? item.content.length : 0;
|
|
110
|
+
|
|
111
|
+
// Base height
|
|
112
|
+
let estimate = 80;
|
|
113
|
+
|
|
114
|
+
// Add height for images (fixed aspect ratio 1.5, width 192px = height ~128px)
|
|
115
|
+
if (hasImages) {
|
|
116
|
+
estimate += item.attachments!.length * 150; // 128px image + margins
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Add height for text content (rough estimate: ~20px per 50 chars)
|
|
120
|
+
if (contentLength > 0) {
|
|
121
|
+
const estimatedLines = Math.ceil(contentLength / 50);
|
|
122
|
+
estimate += estimatedLines * 20;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Assistant messages with markdown tend to be taller
|
|
126
|
+
if (item.role === 'assistant' && contentLength > 200) {
|
|
127
|
+
estimate *= 1.3; // 30% buffer for markdown formatting
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return estimate;
|
|
131
|
+
}, []);
|
|
132
|
+
|
|
133
|
+
return (
|
|
134
|
+
<View className="flex-1 px-4" testID={testID}>
|
|
135
|
+
{messages.length === 0 ? (
|
|
136
|
+
<View
|
|
137
|
+
className="flex-1 items-center justify-center px-6"
|
|
138
|
+
onTouchStart={handleTouchStart}
|
|
139
|
+
>
|
|
140
|
+
{/* <Text className="text-center text-base text-muted-foreground">
|
|
141
|
+
{translate('chatbot.start_conversation')}
|
|
142
|
+
</Text> */}
|
|
143
|
+
</View>
|
|
144
|
+
) : (
|
|
145
|
+
<FlashList<AppMessage>
|
|
146
|
+
ref={attachFlashListRef}
|
|
147
|
+
data={messages}
|
|
148
|
+
renderItem={renderMessage}
|
|
149
|
+
keyExtractor={(item: AppMessage) => item.id}
|
|
150
|
+
getItemType={getItemType}
|
|
151
|
+
drawDistance={1000}
|
|
152
|
+
removeClippedSubviews={false}
|
|
153
|
+
onTouchStart={handleTouchStart}
|
|
154
|
+
onScroll={scrollManager.onScroll}
|
|
155
|
+
onScrollBeginDrag={scrollManager.onScrollBeginDrag}
|
|
156
|
+
onScrollEndDrag={scrollManager.onScrollEndDrag}
|
|
157
|
+
onMomentumScrollBegin={scrollManager.onMomentumScrollBegin}
|
|
158
|
+
onMomentumScrollEnd={scrollManager.onMomentumScrollEnd}
|
|
159
|
+
scrollEventThrottle={16}
|
|
160
|
+
keyboardShouldPersistTaps="handled"
|
|
161
|
+
keyboardDismissMode="on-drag"
|
|
162
|
+
contentContainerStyle={{ paddingTop: 50, paddingBottom: 8 }}
|
|
163
|
+
accessibilityLabel="Chat messages"
|
|
164
|
+
accessibilityRole="list"
|
|
165
|
+
testID="messages-flashlist"
|
|
166
|
+
/>
|
|
167
|
+
)}
|
|
168
|
+
</View>
|
|
169
|
+
);
|
|
170
|
+
};
|
package/recipes/chatbot-supabase/apps/native/src/features/chatbot/components/model-selector.tsx
ADDED
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
import { Feather } from '@expo/vector-icons';
|
|
2
|
+
import type React from 'react';
|
|
3
|
+
import { Alert, ScrollView, View } from 'react-native';
|
|
4
|
+
|
|
5
|
+
import { Pressable, Text } from '@/components/ui';
|
|
6
|
+
import colors from '@/components/ui/colors';
|
|
7
|
+
import { Modal, useModal } from '@/components/ui/core/overlays/modal';
|
|
8
|
+
import type { ModelType, Provider } from '@/features/chatbot/models';
|
|
9
|
+
import {
|
|
10
|
+
getAllProviders,
|
|
11
|
+
getModelById,
|
|
12
|
+
getModelsByProvider,
|
|
13
|
+
getProviderDisplayName,
|
|
14
|
+
getProviderIcon,
|
|
15
|
+
isPremiumModel,
|
|
16
|
+
} from '@/features/chatbot/models';
|
|
17
|
+
import { useEntitlement } from '@/features/payments/hooks/use-entitlement';
|
|
18
|
+
import { useThemeConfig } from '@/lib/use-theme-config';
|
|
19
|
+
|
|
20
|
+
type ModelSelectorProps = {
|
|
21
|
+
selectedModel: ModelType;
|
|
22
|
+
onModelSelect: (model: ModelType) => void;
|
|
23
|
+
testID?: string;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Model selector component that displays available AI models in a beautiful dropdown
|
|
28
|
+
* Replicates the SuperGrok interface design with provider grouping
|
|
29
|
+
*/
|
|
30
|
+
export const ModelSelector: React.FC<ModelSelectorProps> = ({
|
|
31
|
+
selectedModel,
|
|
32
|
+
onModelSelect,
|
|
33
|
+
testID = 'model-selector',
|
|
34
|
+
}) => {
|
|
35
|
+
const theme = useThemeConfig();
|
|
36
|
+
const modal = useModal();
|
|
37
|
+
const { isEntitled: hasPremium } = useEntitlement('premium_access');
|
|
38
|
+
|
|
39
|
+
// Get the current model's display info
|
|
40
|
+
const getCurrentModelInfo = () => {
|
|
41
|
+
const model = getModelById(selectedModel);
|
|
42
|
+
if (model) {
|
|
43
|
+
const IconComponent = getProviderIcon(model.provider);
|
|
44
|
+
return {
|
|
45
|
+
label: model.name,
|
|
46
|
+
provider: model.provider,
|
|
47
|
+
icon: <IconComponent />,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Fallback to OpenAI
|
|
52
|
+
const DefaultIcon = getProviderIcon('openai');
|
|
53
|
+
return {
|
|
54
|
+
label: 'Select Model',
|
|
55
|
+
provider: 'openai' as Provider,
|
|
56
|
+
icon: <DefaultIcon />,
|
|
57
|
+
};
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
const currentModel = getCurrentModelInfo();
|
|
61
|
+
|
|
62
|
+
const handleModelSelect = (model: ModelType) => {
|
|
63
|
+
// Check if model requires premium access
|
|
64
|
+
if (isPremiumModel(model) && !hasPremium) {
|
|
65
|
+
Alert.alert(
|
|
66
|
+
'Premium Required',
|
|
67
|
+
'This model requires a premium subscription. Upgrade to access advanced AI models.',
|
|
68
|
+
[
|
|
69
|
+
{ text: 'Cancel', style: 'cancel' },
|
|
70
|
+
{
|
|
71
|
+
text: 'Upgrade',
|
|
72
|
+
onPress: () => {
|
|
73
|
+
/* Navigate to upgrade */
|
|
74
|
+
},
|
|
75
|
+
},
|
|
76
|
+
],
|
|
77
|
+
);
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
onModelSelect(model);
|
|
82
|
+
modal.dismiss();
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
return (
|
|
86
|
+
<>
|
|
87
|
+
<Pressable
|
|
88
|
+
onPress={modal.present}
|
|
89
|
+
className="flex-row items-center gap-2.5 rounded-full border border-neutral-300/60 bg-neutral-50 px-4 py-3.5 dark:border-neutral-800 dark:bg-neutral-800"
|
|
90
|
+
style={{ alignSelf: 'flex-start' }}
|
|
91
|
+
accessibilityLabel={`Current model: ${currentModel.label}`}
|
|
92
|
+
accessibilityHint="Tap to change AI model"
|
|
93
|
+
accessibilityRole="button"
|
|
94
|
+
testID={testID}
|
|
95
|
+
>
|
|
96
|
+
<View className="size-4">{currentModel.icon}</View>
|
|
97
|
+
<Text
|
|
98
|
+
className="max-w-[120px] text-xs font-medium text-neutral-800 dark:text-neutral-200"
|
|
99
|
+
numberOfLines={1}
|
|
100
|
+
>
|
|
101
|
+
{currentModel.label}
|
|
102
|
+
</Text>
|
|
103
|
+
<Feather
|
|
104
|
+
name="chevron-down"
|
|
105
|
+
size={14}
|
|
106
|
+
color={theme.dark ? colors.neutral[400] : colors.neutral[500]}
|
|
107
|
+
/>
|
|
108
|
+
</Pressable>
|
|
109
|
+
<Modal
|
|
110
|
+
ref={modal.ref}
|
|
111
|
+
index={0}
|
|
112
|
+
snapPoints={['70%']}
|
|
113
|
+
// title="Choose Model"
|
|
114
|
+
>
|
|
115
|
+
<ScrollView
|
|
116
|
+
className="flex-1"
|
|
117
|
+
showsVerticalScrollIndicator={false}
|
|
118
|
+
contentContainerStyle={{
|
|
119
|
+
paddingHorizontal: 24,
|
|
120
|
+
// paddingVertical: 8,
|
|
121
|
+
}}
|
|
122
|
+
>
|
|
123
|
+
{/* Subtitle */}
|
|
124
|
+
<View className="mb-6">
|
|
125
|
+
<Text className="text-center text-xl font-bold">
|
|
126
|
+
Select the AI model
|
|
127
|
+
</Text>
|
|
128
|
+
</View>
|
|
129
|
+
|
|
130
|
+
{getAllProviders().map((provider) => {
|
|
131
|
+
const IconComponent = getProviderIcon(provider);
|
|
132
|
+
const models = getModelsByProvider(provider);
|
|
133
|
+
|
|
134
|
+
return (
|
|
135
|
+
<View key={provider} className="mb-6">
|
|
136
|
+
<View className="mb-4 flex-row items-center gap-3">
|
|
137
|
+
<View className="size-6">
|
|
138
|
+
<IconComponent />
|
|
139
|
+
</View>
|
|
140
|
+
<Text className="text-lg font-semibold text-neutral-900 dark:text-white">
|
|
141
|
+
{getProviderDisplayName(provider)}
|
|
142
|
+
</Text>
|
|
143
|
+
</View>
|
|
144
|
+
{models.map((model) => (
|
|
145
|
+
<ModelOption
|
|
146
|
+
key={model.id}
|
|
147
|
+
model={{
|
|
148
|
+
label: model.name,
|
|
149
|
+
value: model.id,
|
|
150
|
+
description: model.description,
|
|
151
|
+
}}
|
|
152
|
+
isSelected={selectedModel === model.id}
|
|
153
|
+
onSelect={() => handleModelSelect(model.id)}
|
|
154
|
+
isPremium={isPremiumModel(model.id)}
|
|
155
|
+
hasPremium={hasPremium}
|
|
156
|
+
testID={`${testID}-${model.id}`}
|
|
157
|
+
/>
|
|
158
|
+
))}
|
|
159
|
+
</View>
|
|
160
|
+
);
|
|
161
|
+
})}
|
|
162
|
+
</ScrollView>
|
|
163
|
+
</Modal>
|
|
164
|
+
</>
|
|
165
|
+
);
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
type ModelOptionProps = {
|
|
169
|
+
model: { label: string; value: string | number; description?: string };
|
|
170
|
+
isSelected: boolean;
|
|
171
|
+
onSelect: () => void;
|
|
172
|
+
isPremium: boolean;
|
|
173
|
+
hasPremium: boolean;
|
|
174
|
+
testID?: string;
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
const ModelOption: React.FC<ModelOptionProps> = ({
|
|
178
|
+
model,
|
|
179
|
+
isSelected,
|
|
180
|
+
onSelect,
|
|
181
|
+
isPremium,
|
|
182
|
+
hasPremium,
|
|
183
|
+
testID,
|
|
184
|
+
}) => {
|
|
185
|
+
const theme = useThemeConfig();
|
|
186
|
+
const isDisabled = isPremium && !hasPremium;
|
|
187
|
+
|
|
188
|
+
return (
|
|
189
|
+
<Pressable
|
|
190
|
+
onPress={onSelect}
|
|
191
|
+
disabled={isDisabled}
|
|
192
|
+
className="mb-3 rounded-2xl border p-4"
|
|
193
|
+
style={{
|
|
194
|
+
backgroundColor: isDisabled
|
|
195
|
+
? theme.dark
|
|
196
|
+
? 'rgba(51, 65, 85, 0.2)'
|
|
197
|
+
: 'rgba(248, 250, 252, 0.2)'
|
|
198
|
+
: isSelected
|
|
199
|
+
? theme.dark
|
|
200
|
+
? 'rgba(59, 130, 246, 0.25)'
|
|
201
|
+
: 'rgba(59, 130, 246, 0.15)'
|
|
202
|
+
: theme.dark
|
|
203
|
+
? 'rgba(51, 65, 85, 0.2)'
|
|
204
|
+
: 'rgba(248, 250, 252, 0.2)', // 20% opacity for non-selected
|
|
205
|
+
borderColor: isDisabled
|
|
206
|
+
? theme.dark
|
|
207
|
+
? colors.neutral[600]
|
|
208
|
+
: colors.neutral[300]
|
|
209
|
+
: isSelected
|
|
210
|
+
? colors.blue[500]
|
|
211
|
+
: theme.dark
|
|
212
|
+
? colors.neutral[700]
|
|
213
|
+
: colors.neutral[200],
|
|
214
|
+
borderWidth: isSelected ? 2 : 1,
|
|
215
|
+
shadowColor: isSelected ? colors.blue[500] : colors.neutral[900],
|
|
216
|
+
shadowOffset: { width: 0, height: 2 },
|
|
217
|
+
shadowOpacity: isSelected ? 0.15 : 0.05,
|
|
218
|
+
shadowRadius: 8,
|
|
219
|
+
elevation: isSelected ? 4 : 2,
|
|
220
|
+
opacity: isDisabled ? 0.6 : 1,
|
|
221
|
+
}}
|
|
222
|
+
accessibilityLabel={`${model.label}: ${model.description || ''}${isPremium ? ' (Premium)' : ''}`}
|
|
223
|
+
accessibilityHint={
|
|
224
|
+
isDisabled
|
|
225
|
+
? 'Premium required'
|
|
226
|
+
: isSelected
|
|
227
|
+
? 'Currently selected'
|
|
228
|
+
: 'Tap to select'
|
|
229
|
+
}
|
|
230
|
+
accessibilityRole="button"
|
|
231
|
+
testID={testID}
|
|
232
|
+
>
|
|
233
|
+
<View className="flex-row items-center justify-between">
|
|
234
|
+
<View className="flex-1">
|
|
235
|
+
<View className="flex-row items-center gap-2">
|
|
236
|
+
<Text
|
|
237
|
+
className={`text-base font-bold ${
|
|
238
|
+
isDisabled
|
|
239
|
+
? 'text-neutral-500 dark:text-neutral-500'
|
|
240
|
+
: isSelected
|
|
241
|
+
? 'text-blue-700 dark:text-blue-300'
|
|
242
|
+
: 'text-neutral-900 dark:text-white'
|
|
243
|
+
}`}
|
|
244
|
+
>
|
|
245
|
+
{model.label}
|
|
246
|
+
</Text>
|
|
247
|
+
{isPremium && (
|
|
248
|
+
<View className="rounded-full bg-warning-500 px-2 py-0.5">
|
|
249
|
+
<Text className="text-xs font-bold text-white">PRO</Text>
|
|
250
|
+
</View>
|
|
251
|
+
)}
|
|
252
|
+
</View>
|
|
253
|
+
<Text
|
|
254
|
+
className={`mt-1 text-sm ${
|
|
255
|
+
isDisabled
|
|
256
|
+
? 'text-neutral-500 dark:text-neutral-500'
|
|
257
|
+
: isSelected
|
|
258
|
+
? 'text-blue-600 dark:text-blue-400'
|
|
259
|
+
: 'text-neutral-600 dark:text-neutral-400'
|
|
260
|
+
}`}
|
|
261
|
+
>
|
|
262
|
+
{model.description || 'AI model'}
|
|
263
|
+
</Text>
|
|
264
|
+
{isDisabled && (
|
|
265
|
+
<Text className="mt-1 text-xs text-warning-600 dark:text-warning-400">
|
|
266
|
+
Upgrade to Premium to access this model
|
|
267
|
+
</Text>
|
|
268
|
+
)}
|
|
269
|
+
</View>
|
|
270
|
+
{isSelected && !isDisabled && (
|
|
271
|
+
<View className="ml-3 rounded-full bg-blue-500 p-1">
|
|
272
|
+
<Feather name="check" size={16} color={colors.white} />
|
|
273
|
+
</View>
|
|
274
|
+
)}
|
|
275
|
+
{isDisabled && (
|
|
276
|
+
<View className="ml-3">
|
|
277
|
+
<Feather name="lock" size={20} color={colors.neutral[400]} />
|
|
278
|
+
</View>
|
|
279
|
+
)}
|
|
280
|
+
</View>
|
|
281
|
+
</Pressable>
|
|
282
|
+
);
|
|
283
|
+
};
|