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,122 @@
|
|
|
1
|
+
import * as ImagePicker from 'expo-image-picker';
|
|
2
|
+
import { useCallback } from 'react';
|
|
3
|
+
import { Alert } from 'react-native';
|
|
4
|
+
|
|
5
|
+
// Basic image type without upload state (added later in chat-input-bar)
|
|
6
|
+
export type BasicSelectedImage = {
|
|
7
|
+
uri: string;
|
|
8
|
+
type: string;
|
|
9
|
+
fileName?: string;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export const useImagePicker = () => {
|
|
13
|
+
const handleImagePicker = useCallback(async (): Promise<
|
|
14
|
+
BasicSelectedImage[]
|
|
15
|
+
> => {
|
|
16
|
+
try {
|
|
17
|
+
// Request permissions
|
|
18
|
+
const { status } =
|
|
19
|
+
await ImagePicker.requestMediaLibraryPermissionsAsync();
|
|
20
|
+
if (status !== 'granted') {
|
|
21
|
+
Alert.alert(
|
|
22
|
+
'Permission Required',
|
|
23
|
+
'Sorry, we need camera roll permissions to upload images.',
|
|
24
|
+
);
|
|
25
|
+
return [];
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Launch image picker with multiple selection enabled
|
|
29
|
+
const result = await ImagePicker.launchImageLibraryAsync({
|
|
30
|
+
mediaTypes: ImagePicker.MediaTypeOptions.Images,
|
|
31
|
+
allowsMultipleSelection: true,
|
|
32
|
+
allowsEditing: false,
|
|
33
|
+
aspect: [4, 3],
|
|
34
|
+
quality: 0.8,
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
if (!result.canceled && result.assets && result.assets.length > 0) {
|
|
38
|
+
return result.assets.map((asset) => ({
|
|
39
|
+
uri: asset.uri,
|
|
40
|
+
type: asset.mimeType || 'image/jpeg',
|
|
41
|
+
fileName: asset.fileName || undefined,
|
|
42
|
+
}));
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return [];
|
|
46
|
+
} catch (error) {
|
|
47
|
+
console.error('Error picking images:', error);
|
|
48
|
+
Alert.alert('Error', 'Failed to pick images. Please try again.');
|
|
49
|
+
return [];
|
|
50
|
+
}
|
|
51
|
+
}, []);
|
|
52
|
+
|
|
53
|
+
const handleCamera =
|
|
54
|
+
useCallback(async (): Promise<BasicSelectedImage | null> => {
|
|
55
|
+
try {
|
|
56
|
+
// Request permissions
|
|
57
|
+
const { status } = await ImagePicker.requestCameraPermissionsAsync();
|
|
58
|
+
if (status !== 'granted') {
|
|
59
|
+
Alert.alert(
|
|
60
|
+
'Permission Required',
|
|
61
|
+
'Sorry, we need camera permissions to take photos.',
|
|
62
|
+
);
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Launch camera
|
|
67
|
+
const result = await ImagePicker.launchCameraAsync({
|
|
68
|
+
allowsEditing: true,
|
|
69
|
+
aspect: [4, 3],
|
|
70
|
+
quality: 0.8,
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
if (!result.canceled && result.assets[0]) {
|
|
74
|
+
const asset = result.assets[0];
|
|
75
|
+
return {
|
|
76
|
+
uri: asset.uri,
|
|
77
|
+
type: asset.mimeType || 'image/jpeg',
|
|
78
|
+
fileName: asset.fileName || undefined,
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return null;
|
|
83
|
+
} catch (error) {
|
|
84
|
+
console.error('Error taking photo:', error);
|
|
85
|
+
Alert.alert('Error', 'Failed to take photo. Please try again.');
|
|
86
|
+
return null;
|
|
87
|
+
}
|
|
88
|
+
}, []);
|
|
89
|
+
|
|
90
|
+
const showImageSourceOptions = useCallback(
|
|
91
|
+
(onImagesSelected: (images: BasicSelectedImage[]) => void) => {
|
|
92
|
+
Alert.alert('Select Image', 'Choose an image source:', [
|
|
93
|
+
{
|
|
94
|
+
text: 'Camera',
|
|
95
|
+
onPress: async () => {
|
|
96
|
+
const image = await handleCamera();
|
|
97
|
+
if (image) {
|
|
98
|
+
onImagesSelected([image]);
|
|
99
|
+
}
|
|
100
|
+
},
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
text: 'Photo Library',
|
|
104
|
+
onPress: async () => {
|
|
105
|
+
const images = await handleImagePicker();
|
|
106
|
+
if (images.length > 0) {
|
|
107
|
+
onImagesSelected(images);
|
|
108
|
+
}
|
|
109
|
+
},
|
|
110
|
+
},
|
|
111
|
+
{ text: 'Cancel', style: 'cancel' },
|
|
112
|
+
]);
|
|
113
|
+
},
|
|
114
|
+
[handleCamera, handleImagePicker],
|
|
115
|
+
);
|
|
116
|
+
|
|
117
|
+
return {
|
|
118
|
+
handleImagePicker,
|
|
119
|
+
handleCamera,
|
|
120
|
+
showImageSourceOptions,
|
|
121
|
+
};
|
|
122
|
+
};
|
package/recipes/chatbot-supabase/apps/native/src/features/chatbot/hooks/use-keyboard-coordinator.ts
ADDED
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import { useCallback, useEffect, useRef } from 'react';
|
|
2
|
+
import { Keyboard } from 'react-native';
|
|
3
|
+
import {
|
|
4
|
+
useAnimatedStyle,
|
|
5
|
+
useSharedValue,
|
|
6
|
+
withTiming,
|
|
7
|
+
} from 'react-native-reanimated';
|
|
8
|
+
import { runOnUI } from 'react-native-worklets';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Hook to coordinate keyboard animations and padding.
|
|
12
|
+
* Eliminates race conditions in keyboard show/hide animations.
|
|
13
|
+
*
|
|
14
|
+
* Features:
|
|
15
|
+
* - Smooth 300ms animations using Reanimated
|
|
16
|
+
* - Cancels pending animations to prevent stacking
|
|
17
|
+
* - Synchronized timing with message submission
|
|
18
|
+
* - Automatic cleanup on unmount
|
|
19
|
+
* - 60fps animations using runOnUI
|
|
20
|
+
*
|
|
21
|
+
* Requirements addressed:
|
|
22
|
+
* - 3.1: Animate padding smoothly over 300ms
|
|
23
|
+
* - 3.2: Reset padding to zero with synchronized timing
|
|
24
|
+
* - 3.3: Dismiss keyboard and reset padding atomically on message send
|
|
25
|
+
* - 3.4: Cancel pending animations to prevent stacking
|
|
26
|
+
* - 3.5: Prioritize submission action over animation completion
|
|
27
|
+
*
|
|
28
|
+
* @returns Object containing animated padding value, reset function, and animated style
|
|
29
|
+
*
|
|
30
|
+
* @example
|
|
31
|
+
* ```tsx
|
|
32
|
+
* const keyboardCoordinator = useKeyboardCoordinator();
|
|
33
|
+
*
|
|
34
|
+
* // Use animated style for keyboard padding
|
|
35
|
+
* <Animated.View style={keyboardCoordinator.keyboardPadding} />
|
|
36
|
+
*
|
|
37
|
+
* // Reset padding when sending message
|
|
38
|
+
* const handleSubmit = async () => {
|
|
39
|
+
* await sendMessage();
|
|
40
|
+
* keyboardCoordinator.resetPadding();
|
|
41
|
+
* };
|
|
42
|
+
* ```
|
|
43
|
+
*/
|
|
44
|
+
export function useKeyboardCoordinator() {
|
|
45
|
+
// Animated padding value for smooth transitions
|
|
46
|
+
const height = useSharedValue(0);
|
|
47
|
+
|
|
48
|
+
// Pending timeout reference for cancellation
|
|
49
|
+
const pendingTimeout = useRef<NodeJS.Timeout | null>(null);
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Cancel any pending animations.
|
|
53
|
+
* Prevents animation stacking when rapid keyboard events occur.
|
|
54
|
+
*/
|
|
55
|
+
const cancelPending = useCallback(() => {
|
|
56
|
+
if (pendingTimeout.current) {
|
|
57
|
+
clearTimeout(pendingTimeout.current);
|
|
58
|
+
pendingTimeout.current = null;
|
|
59
|
+
}
|
|
60
|
+
}, []);
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Reset padding to zero with smooth animation.
|
|
64
|
+
* Uses runOnUI for 60fps performance.
|
|
65
|
+
* Cancels any pending animations before starting new one.
|
|
66
|
+
*/
|
|
67
|
+
const resetPadding = useCallback(() => {
|
|
68
|
+
cancelPending();
|
|
69
|
+
|
|
70
|
+
runOnUI(() => {
|
|
71
|
+
'worklet';
|
|
72
|
+
height.value = withTiming(0, { duration: 300 });
|
|
73
|
+
})();
|
|
74
|
+
}, [height, cancelPending]);
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Handle keyboard show event.
|
|
78
|
+
* Animates padding to match keyboard height.
|
|
79
|
+
*
|
|
80
|
+
* @param keyboardHeight - Height of the keyboard in pixels
|
|
81
|
+
*/
|
|
82
|
+
const onKeyboardShow = useCallback(
|
|
83
|
+
(keyboardHeight: number) => {
|
|
84
|
+
cancelPending();
|
|
85
|
+
|
|
86
|
+
runOnUI(() => {
|
|
87
|
+
'worklet';
|
|
88
|
+
height.value = withTiming(keyboardHeight, { duration: 300 });
|
|
89
|
+
})();
|
|
90
|
+
},
|
|
91
|
+
[height, cancelPending],
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Handle keyboard hide event.
|
|
96
|
+
* Resets padding to zero with animation.
|
|
97
|
+
*/
|
|
98
|
+
const onKeyboardHide = useCallback(() => {
|
|
99
|
+
resetPadding();
|
|
100
|
+
}, [resetPadding]);
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Animated style for keyboard padding.
|
|
104
|
+
* Apply this to a View component to create dynamic spacing.
|
|
105
|
+
*/
|
|
106
|
+
const keyboardPadding = useAnimatedStyle(() => {
|
|
107
|
+
return {
|
|
108
|
+
height: height.value,
|
|
109
|
+
};
|
|
110
|
+
}, [height]);
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Set up keyboard event listeners.
|
|
114
|
+
* Uses keyboardWillShow for instant response (iOS) and keyboardDidShow as fallback (Android).
|
|
115
|
+
* Automatically cleans up on unmount.
|
|
116
|
+
*/
|
|
117
|
+
useEffect(() => {
|
|
118
|
+
// Use 'Will' events for instant response on iOS, 'Did' events as fallback for Android
|
|
119
|
+
const showListener = Keyboard.addListener('keyboardWillShow', (e) => {
|
|
120
|
+
onKeyboardShow(e.endCoordinates.height);
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
const hideListener = Keyboard.addListener(
|
|
124
|
+
'keyboardWillHide',
|
|
125
|
+
onKeyboardHide,
|
|
126
|
+
);
|
|
127
|
+
|
|
128
|
+
// Fallback for Android (doesn't have 'Will' events)
|
|
129
|
+
const showListenerAndroid = Keyboard.addListener('keyboardDidShow', (e) => {
|
|
130
|
+
onKeyboardShow(e.endCoordinates.height);
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
const hideListenerAndroid = Keyboard.addListener(
|
|
134
|
+
'keyboardDidHide',
|
|
135
|
+
onKeyboardHide,
|
|
136
|
+
);
|
|
137
|
+
|
|
138
|
+
return () => {
|
|
139
|
+
showListener.remove();
|
|
140
|
+
hideListener.remove();
|
|
141
|
+
showListenerAndroid.remove();
|
|
142
|
+
hideListenerAndroid.remove();
|
|
143
|
+
cancelPending();
|
|
144
|
+
};
|
|
145
|
+
}, [onKeyboardShow, onKeyboardHide, cancelPending]);
|
|
146
|
+
|
|
147
|
+
return {
|
|
148
|
+
// Animated values
|
|
149
|
+
height,
|
|
150
|
+
keyboardPadding,
|
|
151
|
+
|
|
152
|
+
// Methods
|
|
153
|
+
resetPadding,
|
|
154
|
+
cancelPending,
|
|
155
|
+
onKeyboardShow,
|
|
156
|
+
onKeyboardHide,
|
|
157
|
+
|
|
158
|
+
// Refs
|
|
159
|
+
pendingTimeout,
|
|
160
|
+
};
|
|
161
|
+
}
|
package/recipes/chatbot-supabase/apps/native/src/features/chatbot/hooks/use-smart-scroll-manager.ts
ADDED
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
import type { FlashListRef } from '@shopify/flash-list';
|
|
2
|
+
import { useCallback, useEffect, useMemo, useRef } from 'react';
|
|
3
|
+
import type { NativeScrollEvent, NativeSyntheticEvent } from 'react-native';
|
|
4
|
+
|
|
5
|
+
const NEAR_BOTTOM_THRESHOLD = 120;
|
|
6
|
+
const SCROLL_DEBOUNCE_MS = 32;
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Smart scroll manager for chat message lists.
|
|
10
|
+
* Handles auto-scrolling based on user intent and scroll position.
|
|
11
|
+
*
|
|
12
|
+
* Features:
|
|
13
|
+
* - Auto-scroll when user is near bottom (within 120px threshold)
|
|
14
|
+
* - Cancel auto-scroll when user manually scrolls up
|
|
15
|
+
* - Always scroll on user message send
|
|
16
|
+
* - Track drag state to prevent scroll interruption
|
|
17
|
+
* - Debounced scroll scheduling (32ms) for performance
|
|
18
|
+
*
|
|
19
|
+
* Requirements addressed:
|
|
20
|
+
* - 4.1: Auto-scroll when near bottom (120px threshold)
|
|
21
|
+
* - 4.2: Don't auto-scroll when user scrolls up to read history
|
|
22
|
+
* - 4.3: Always scroll to bottom when user sends a message
|
|
23
|
+
* - 4.4: Maintain scroll position during streaming if reading history
|
|
24
|
+
* - 4.5: Cancel pending auto-scroll operations when user drags
|
|
25
|
+
*
|
|
26
|
+
* @param flashListRef - Reference to the FlashList component
|
|
27
|
+
* @returns Object containing scroll state refs, methods, and event handlers
|
|
28
|
+
*
|
|
29
|
+
* @example
|
|
30
|
+
* ```tsx
|
|
31
|
+
* const flashListRef = useRef<FlashListRef<Message> | null>(null);
|
|
32
|
+
* const scrollManager = useSmartScrollManager(flashListRef);
|
|
33
|
+
*
|
|
34
|
+
* // In useEffect for message updates
|
|
35
|
+
* useEffect(() => {
|
|
36
|
+
* scrollManager.handleMessagesUpdate(messages, previousMessageIds, { logTelemetry: true });
|
|
37
|
+
* previousMessageIds.current = messages.map(m => m.id);
|
|
38
|
+
* }, [messages, scrollManager]);
|
|
39
|
+
*
|
|
40
|
+
* // In FlashList props
|
|
41
|
+
* <FlashList
|
|
42
|
+
* onScroll={scrollManager.onScroll}
|
|
43
|
+
* onScrollBeginDrag={scrollManager.onScrollBeginDrag}
|
|
44
|
+
* onScrollEndDrag={scrollManager.onScrollEndDrag}
|
|
45
|
+
* onMomentumScrollBegin={scrollManager.onMomentumScrollBegin}
|
|
46
|
+
* onMomentumScrollEnd={scrollManager.onMomentumScrollEnd}
|
|
47
|
+
* />
|
|
48
|
+
* ```
|
|
49
|
+
*/
|
|
50
|
+
export function useSmartScrollManager<T>(
|
|
51
|
+
flashListRef: React.RefObject<FlashListRef<T> | null>,
|
|
52
|
+
) {
|
|
53
|
+
// Track if user is near bottom (within 120px)
|
|
54
|
+
const isNearBottom = useRef(true);
|
|
55
|
+
|
|
56
|
+
// Track if user is actively dragging
|
|
57
|
+
const isDragging = useRef(false);
|
|
58
|
+
|
|
59
|
+
// Pending scroll timeout for debouncing
|
|
60
|
+
const pendingScroll = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Cancel any pending scroll operation
|
|
64
|
+
*/
|
|
65
|
+
const cancelScroll = useCallback(() => {
|
|
66
|
+
if (pendingScroll.current) {
|
|
67
|
+
clearTimeout(pendingScroll.current);
|
|
68
|
+
pendingScroll.current = null;
|
|
69
|
+
}
|
|
70
|
+
}, []);
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Scroll to bottom immediately
|
|
74
|
+
* Includes null check to prevent crashes when component unmounts
|
|
75
|
+
*/
|
|
76
|
+
const scrollToBottom = useCallback(() => {
|
|
77
|
+
if (flashListRef.current) {
|
|
78
|
+
try {
|
|
79
|
+
flashListRef.current.scrollToEnd({ animated: true });
|
|
80
|
+
isNearBottom.current = true;
|
|
81
|
+
} catch (error) {
|
|
82
|
+
// Silently catch scroll errors (e.g., if list is unmounting)
|
|
83
|
+
console.warn('[ScrollManager] Scroll failed:', error);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}, [flashListRef]);
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Schedule scroll to bottom with 32ms debounce
|
|
90
|
+
* Cancels any pending scroll before scheduling new one
|
|
91
|
+
* This prevents rapid scroll calls during streaming
|
|
92
|
+
*/
|
|
93
|
+
const scheduleScroll = useCallback(() => {
|
|
94
|
+
// If already pending, don't schedule another one
|
|
95
|
+
if (pendingScroll.current) {
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
pendingScroll.current = setTimeout(() => {
|
|
100
|
+
pendingScroll.current = null;
|
|
101
|
+
scrollToBottom();
|
|
102
|
+
}, SCROLL_DEBOUNCE_MS);
|
|
103
|
+
}, [scrollToBottom]);
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Handle scroll events to track near-bottom state
|
|
107
|
+
* Updates isNearBottom based on 120px threshold
|
|
108
|
+
*/
|
|
109
|
+
const onScroll = useCallback(
|
|
110
|
+
(event: NativeSyntheticEvent<NativeScrollEvent>) => {
|
|
111
|
+
const { contentOffset, contentSize, layoutMeasurement } =
|
|
112
|
+
event.nativeEvent;
|
|
113
|
+
const visibleBottom = contentOffset.y + layoutMeasurement.height;
|
|
114
|
+
const distanceFromBottom = Math.max(
|
|
115
|
+
contentSize.height - visibleBottom,
|
|
116
|
+
0,
|
|
117
|
+
);
|
|
118
|
+
|
|
119
|
+
isNearBottom.current = distanceFromBottom < NEAR_BOTTOM_THRESHOLD;
|
|
120
|
+
},
|
|
121
|
+
[],
|
|
122
|
+
);
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Handle scroll begin drag - cancel pending scrolls
|
|
126
|
+
*/
|
|
127
|
+
const onScrollBeginDrag = useCallback(() => {
|
|
128
|
+
isDragging.current = true;
|
|
129
|
+
cancelScroll();
|
|
130
|
+
}, [cancelScroll]);
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Handle scroll end drag - mark dragging as complete
|
|
134
|
+
*/
|
|
135
|
+
const onScrollEndDrag = useCallback(() => {
|
|
136
|
+
isDragging.current = false;
|
|
137
|
+
}, []);
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Handle momentum scroll begin - treat as dragging
|
|
141
|
+
*/
|
|
142
|
+
const onMomentumScrollBegin = useCallback(() => {
|
|
143
|
+
isDragging.current = true;
|
|
144
|
+
}, []);
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Handle momentum scroll end - mark dragging as complete
|
|
148
|
+
*/
|
|
149
|
+
const onMomentumScrollEnd = useCallback(() => {
|
|
150
|
+
isDragging.current = false;
|
|
151
|
+
}, []);
|
|
152
|
+
|
|
153
|
+
const markNearBottom = useCallback(() => {
|
|
154
|
+
isNearBottom.current = true;
|
|
155
|
+
}, []);
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Determine if should auto-scroll based on current state
|
|
159
|
+
* Always scrolls on user message send
|
|
160
|
+
* Otherwise only scrolls if user is near bottom and not dragging
|
|
161
|
+
*/
|
|
162
|
+
const shouldAutoScroll = useCallback((userSentMessage: boolean): boolean => {
|
|
163
|
+
if (userSentMessage) {
|
|
164
|
+
return true;
|
|
165
|
+
}
|
|
166
|
+
return !isDragging.current && isNearBottom.current;
|
|
167
|
+
}, []);
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Cleanup pending scrolls on unmount
|
|
171
|
+
*/
|
|
172
|
+
useEffect(() => {
|
|
173
|
+
return () => {
|
|
174
|
+
cancelScroll();
|
|
175
|
+
};
|
|
176
|
+
}, [cancelScroll]);
|
|
177
|
+
|
|
178
|
+
// Memoize the return object to prevent unnecessary re-renders
|
|
179
|
+
return useMemo(
|
|
180
|
+
() => ({
|
|
181
|
+
// State refs
|
|
182
|
+
isNearBottom,
|
|
183
|
+
isDragging,
|
|
184
|
+
pendingScroll,
|
|
185
|
+
|
|
186
|
+
// Methods
|
|
187
|
+
scheduleScroll,
|
|
188
|
+
markNearBottom,
|
|
189
|
+
cancelScroll,
|
|
190
|
+
scrollToBottom,
|
|
191
|
+
shouldAutoScroll,
|
|
192
|
+
|
|
193
|
+
// Event handlers
|
|
194
|
+
onScroll,
|
|
195
|
+
onScrollBeginDrag,
|
|
196
|
+
onScrollEndDrag,
|
|
197
|
+
onMomentumScrollBegin,
|
|
198
|
+
onMomentumScrollEnd,
|
|
199
|
+
}),
|
|
200
|
+
[
|
|
201
|
+
scheduleScroll,
|
|
202
|
+
markNearBottom,
|
|
203
|
+
cancelScroll,
|
|
204
|
+
scrollToBottom,
|
|
205
|
+
shouldAutoScroll,
|
|
206
|
+
onScroll,
|
|
207
|
+
onScrollBeginDrag,
|
|
208
|
+
onScrollEndDrag,
|
|
209
|
+
onMomentumScrollBegin,
|
|
210
|
+
onMomentumScrollEnd,
|
|
211
|
+
],
|
|
212
|
+
);
|
|
213
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
// Import directly to avoid circular dependencies
|
|
2
|
+
import { DEFAULT_MODELS, MODELS } from './models';
|
|
3
|
+
import type { ModelConfig, ModelId, Provider } from './types';
|
|
4
|
+
|
|
5
|
+
// Re-export models and model functions
|
|
6
|
+
export { DEFAULT_MODELS, getModelDescription, MODELS } from './models';
|
|
7
|
+
// Re-export provider functions
|
|
8
|
+
export {
|
|
9
|
+
getAllProviders,
|
|
10
|
+
getProviderConfig,
|
|
11
|
+
getProviderDisplayName,
|
|
12
|
+
getProviderIcon,
|
|
13
|
+
PROVIDER_CONFIGS,
|
|
14
|
+
} from './providers';
|
|
15
|
+
// Re-export types
|
|
16
|
+
export type {
|
|
17
|
+
ModelCategory,
|
|
18
|
+
ModelConfig,
|
|
19
|
+
ModelId,
|
|
20
|
+
ModelOptions,
|
|
21
|
+
ModelSpeed,
|
|
22
|
+
Provider,
|
|
23
|
+
ProviderConfig,
|
|
24
|
+
} from './types';
|
|
25
|
+
|
|
26
|
+
// Additional helper functions
|
|
27
|
+
export const getModelById = (id: string): ModelConfig | undefined => {
|
|
28
|
+
return MODELS.find((model) => model.id === id);
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export const getModelsByProvider = (provider: Provider): ModelConfig[] => {
|
|
32
|
+
return MODELS.filter((model) => model.provider === provider);
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export const getPremiumModels = (): ModelConfig[] => {
|
|
36
|
+
return MODELS.filter((model) => model.category === 'premium');
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export const getFreeModels = (): ModelConfig[] => {
|
|
40
|
+
return MODELS.filter((model) => model.category === 'free');
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
export const getDefaultModel = (provider: Provider): ModelConfig => {
|
|
44
|
+
const defaultId = DEFAULT_MODELS[provider];
|
|
45
|
+
const model = getModelById(defaultId);
|
|
46
|
+
if (!model) {
|
|
47
|
+
throw new Error(
|
|
48
|
+
`Default model ${defaultId} not found for provider ${provider}`,
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
return model;
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
export const isPremiumModel = (modelId: string): boolean => {
|
|
55
|
+
const model = getModelById(modelId);
|
|
56
|
+
return model?.category === 'premium';
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
export const getAllModelIds = (): string[] => {
|
|
60
|
+
return MODELS.map((model: any) => model.id);
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
export const getModelsByCategory = (
|
|
64
|
+
category: 'premium' | 'free' | 'specialized',
|
|
65
|
+
) => {
|
|
66
|
+
return MODELS.filter((model: any) => model.category === category);
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
export const getModelProvider = (modelId: string): string | undefined => {
|
|
70
|
+
const model = getModelById(modelId);
|
|
71
|
+
return model?.provider;
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
// Backward compatibility exports (to match old API) - lazy evaluation
|
|
75
|
+
export const ALL_MODELS = (): string[] => {
|
|
76
|
+
return MODELS.map((model: any) => model.id);
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
export const PREMIUM_MODELS = (): string[] => {
|
|
80
|
+
return MODELS.filter((model: any) => model.category === 'premium').map(
|
|
81
|
+
(model: any) => model.id,
|
|
82
|
+
);
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
// Legacy type for backward compatibility
|
|
86
|
+
export type ModelType = ModelId;
|