vibefast-cli 1.1.5 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +32 -0
- package/README.md +63 -169
- package/dist/__tests__/recipes.test.js +89 -85
- package/dist/__tests__/recipes.test.js.map +1 -1
- package/dist/commands/add.d.ts +1 -1
- package/dist/commands/add.d.ts.map +1 -1
- package/dist/commands/add.js +576 -588
- package/dist/commands/add.js.map +1 -1
- package/dist/commands/checklist.d.ts +1 -1
- package/dist/commands/checklist.d.ts.map +1 -1
- package/dist/commands/checklist.js +40 -39
- package/dist/commands/checklist.js.map +1 -1
- package/dist/commands/doctor.d.ts +1 -1
- package/dist/commands/doctor.js +22 -22
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/env.d.ts +1 -1
- package/dist/commands/env.d.ts.map +1 -1
- package/dist/commands/env.js +58 -53
- package/dist/commands/env.js.map +1 -1
- package/dist/commands/health.d.ts +1 -1
- package/dist/commands/health.d.ts.map +1 -1
- package/dist/commands/health.js +101 -93
- package/dist/commands/health.js.map +1 -1
- package/dist/commands/init.d.ts +1 -1
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +416 -296
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/remove.d.ts +1 -1
- package/dist/commands/remove.d.ts.map +1 -1
- package/dist/commands/remove.js +77 -64
- package/dist/commands/remove.js.map +1 -1
- package/dist/commands/status.d.ts +1 -1
- package/dist/commands/status.d.ts.map +1 -1
- package/dist/commands/status.js +15 -14
- package/dist/commands/status.js.map +1 -1
- package/dist/core/__tests__/detect.test.js +68 -34
- package/dist/core/__tests__/detect.test.js.map +1 -1
- package/dist/core/ast.d.ts +14 -0
- package/dist/core/ast.d.ts.map +1 -0
- package/dist/core/ast.js +239 -0
- package/dist/core/ast.js.map +1 -0
- package/dist/core/codemod.d.ts.map +1 -1
- package/dist/core/codemod.js +62 -44
- package/dist/core/codemod.js.map +1 -1
- package/dist/core/config.d.ts +10 -0
- package/dist/core/config.d.ts.map +1 -0
- package/dist/core/config.js +51 -0
- package/dist/core/config.js.map +1 -0
- package/dist/core/detect.d.ts +8 -2
- package/dist/core/detect.d.ts.map +1 -1
- package/dist/core/detect.js +52 -21
- package/dist/core/detect.js.map +1 -1
- package/dist/core/errors.d.ts.map +1 -1
- package/dist/core/errors.js +9 -8
- package/dist/core/errors.js.map +1 -1
- package/dist/core/exec.d.ts +16 -0
- package/dist/core/exec.d.ts.map +1 -0
- package/dist/core/exec.js +48 -0
- package/dist/core/exec.js.map +1 -0
- package/dist/core/manualSteps.d.ts +7 -0
- package/dist/core/manualSteps.d.ts.map +1 -0
- package/dist/core/manualSteps.js +59 -0
- package/dist/core/manualSteps.js.map +1 -0
- package/dist/core/paths.d.ts +3 -1
- package/dist/core/paths.d.ts.map +1 -1
- package/dist/core/paths.js +14 -10
- package/dist/core/paths.js.map +1 -1
- package/dist/core/spinner.d.ts +1 -1
- package/dist/core/spinner.d.ts.map +1 -1
- package/dist/core/spinner.js +38 -8
- package/dist/core/spinner.js.map +1 -1
- package/dist/core/vosk.d.ts.map +1 -1
- package/dist/core/vosk.js +50 -39
- package/dist/core/vosk.js.map +1 -1
- package/docs/manual-testing.md +91 -0
- package/package.json +6 -3
- package/recipes/audio-recorder/apps/native/src/app/audio-recorder/index.tsx +5 -0
- package/recipes/audio-recorder/recipe.json +3 -3
- package/recipes/audio-recorder-supabase/apps/native/src/features/audio-recorder/components/audio-player.tsx +301 -0
- package/recipes/audio-recorder-supabase/apps/native/src/features/audio-recorder/components/audio-recorder.tsx +373 -0
- package/recipes/audio-recorder-supabase/apps/native/src/features/audio-recorder/components/audio-waveform.tsx +270 -0
- package/recipes/audio-recorder-supabase/apps/native/src/features/audio-recorder/components/index.ts +4 -0
- package/recipes/audio-recorder-supabase/apps/native/src/features/audio-recorder/components/recording-list.tsx +89 -0
- package/recipes/audio-recorder-supabase/apps/native/src/features/audio-recorder/demo/audio-player-demo.tsx +66 -0
- package/recipes/audio-recorder-supabase/apps/native/src/features/audio-recorder/demo/audio-recorder-cloud.tsx +68 -0
- package/recipes/audio-recorder-supabase/apps/native/src/features/audio-recorder/demo/audio-recorder-interview.tsx +102 -0
- package/recipes/audio-recorder-supabase/apps/native/src/features/audio-recorder/demo/basic.tsx +27 -0
- package/recipes/audio-recorder-supabase/apps/native/src/features/audio-recorder/demo/index.ts +5 -0
- package/recipes/audio-recorder-supabase/apps/native/src/features/audio-recorder/demo/with-recording-list-demo.tsx +82 -0
- package/recipes/audio-recorder-supabase/packages/backend/src/services/recordings.ts +369 -0
- package/recipes/audio-recorder-supabase/packages/backend/supabase/migrations/recordings.sql +70 -0
- package/recipes/audio-recorder-supabase/recipe.json +52 -0
- package/recipes/audio-recorder-supabase@latest.zip +0 -0
- package/recipes/audio-recorder@latest.zip +0 -0
- package/recipes/charts/apps/native/src/features/charts/components/bar-chart.tsx +3 -3
- package/recipes/charts/apps/native/src/features/charts/components/candlestick-chart.tsx +2 -2
- package/recipes/charts/apps/native/src/features/charts/components/chart-card.tsx +5 -5
- package/recipes/charts/apps/native/src/features/charts/components/column-chart.tsx +3 -3
- package/recipes/charts/apps/native/src/features/charts/components/doughnut-chart.tsx +20 -4
- package/recipes/charts/apps/native/src/features/charts/components/line-chart.tsx +7 -6
- package/recipes/charts/apps/native/src/features/charts/components/radar-chart.tsx +6 -4
- package/recipes/charts/apps/native/src/features/charts/components/radial-bar-chart.tsx +1 -1
- package/recipes/charts/apps/native/src/features/charts/components/stacked-bar-chart.tsx +5 -4
- package/recipes/charts/recipe.json +4 -13
- package/recipes/charts@latest.zip +0 -0
- package/recipes/chatbot/apps/native/src/app/chatbot/index.tsx +1 -0
- package/recipes/chatbot/apps/native/src/features/chatbot/components/chat-markdown.tsx +86 -86
- package/recipes/chatbot/apps/native/src/features/chatbot/components/markdown/code-block.tsx +86 -53
- package/recipes/chatbot/recipe.json +26 -92
- package/recipes/chatbot-supabase/apps/native/src/api-client/supabase/chatbot.ts +515 -0
- package/recipes/chatbot-supabase/apps/native/src/features/chatbot/app/index.tsx +257 -0
- package/recipes/chatbot-supabase/apps/native/src/features/chatbot/components/chat-header-buttons.tsx +59 -0
- package/recipes/chatbot-supabase/apps/native/src/features/chatbot/components/chat-input-bar.tsx +485 -0
- package/recipes/chatbot-supabase/apps/native/src/features/chatbot/components/chat-markdown.tsx +575 -0
- package/recipes/chatbot-supabase/apps/native/src/features/chatbot/components/chat-message-bubble.tsx +223 -0
- package/recipes/chatbot-supabase/apps/native/src/features/chatbot/components/chat-settings-modal.tsx +161 -0
- package/recipes/chatbot-supabase/apps/native/src/features/chatbot/components/image-preview-list.tsx +116 -0
- package/recipes/chatbot-supabase/apps/native/src/features/chatbot/components/markdown/code-block.tsx +165 -0
- package/recipes/chatbot-supabase/apps/native/src/features/chatbot/components/markdown/index.ts +10 -0
- package/recipes/chatbot-supabase/apps/native/src/features/chatbot/components/markdown/table-renderer.tsx +129 -0
- package/recipes/chatbot-supabase/apps/native/src/features/chatbot/components/message-error-boundary.tsx +78 -0
- package/recipes/chatbot-supabase/apps/native/src/features/chatbot/components/message-list.tsx +170 -0
- package/recipes/chatbot-supabase/apps/native/src/features/chatbot/components/model-selector.tsx +283 -0
- package/recipes/chatbot-supabase/apps/native/src/features/chatbot/components/report-content-modal.tsx +188 -0
- package/recipes/chatbot-supabase/apps/native/src/features/chatbot/components/suggested-messages.tsx +67 -0
- package/recipes/chatbot-supabase/apps/native/src/features/chatbot/constants/models.ts +20 -0
- package/recipes/chatbot-supabase/apps/native/src/features/chatbot/constants/report-reasons.ts +9 -0
- package/recipes/chatbot-supabase/apps/native/src/features/chatbot/hooks/use-attachment-cache.ts +142 -0
- package/recipes/chatbot-supabase/apps/native/src/features/chatbot/hooks/use-chat-config.ts +458 -0
- package/recipes/chatbot-supabase/apps/native/src/features/chatbot/hooks/use-chat-handlers.ts +429 -0
- package/recipes/chatbot-supabase/apps/native/src/features/chatbot/hooks/use-chatbot-settings.ts +89 -0
- package/recipes/chatbot-supabase/apps/native/src/features/chatbot/hooks/use-conversation.ts +90 -0
- package/recipes/chatbot-supabase/apps/native/src/features/chatbot/hooks/use-image-picker.ts +122 -0
- package/recipes/chatbot-supabase/apps/native/src/features/chatbot/hooks/use-keyboard-coordinator.ts +161 -0
- package/recipes/chatbot-supabase/apps/native/src/features/chatbot/hooks/use-smart-scroll-manager.ts +213 -0
- package/recipes/chatbot-supabase/apps/native/src/features/chatbot/models/index.ts +86 -0
- package/recipes/chatbot-supabase/apps/native/src/features/chatbot/models/models.ts +162 -0
- package/recipes/chatbot-supabase/apps/native/src/features/chatbot/models/providers.ts +62 -0
- package/recipes/chatbot-supabase/apps/native/src/features/chatbot/models/types.ts +40 -0
- package/recipes/chatbot-supabase/apps/native/src/features/chatbot/services/file-uploader.ts +287 -0
- package/recipes/chatbot-supabase/apps/native/src/features/chatbot/services/message-handler-service.ts +189 -0
- package/recipes/chatbot-supabase/apps/native/src/features/chatbot/types/index.ts +70 -0
- package/recipes/chatbot-supabase/apps/native/src/features/chatbot/utils/chat-telemetry.ts +91 -0
- package/recipes/chatbot-supabase/packages/backend/src/services/conversations.ts +243 -0
- package/recipes/chatbot-supabase/packages/backend/src/services/messages.ts +327 -0
- package/recipes/chatbot-supabase/packages/backend/supabase/functions/chat-stream/index.ts +347 -0
- package/recipes/chatbot-supabase/packages/backend/supabase/migrations/chatbot.sql +104 -0
- package/recipes/chatbot-supabase/recipe.json +106 -0
- package/recipes/chatbot-supabase@latest.zip +0 -0
- package/recipes/chatbot.zip +0 -0
- package/recipes/chatbot@latest.zip +0 -0
- package/recipes/image-analysis/packages/backend/convex/imageAnalysis/index.ts +2 -2
- package/recipes/image-analysis/packages/backend/convex/imageAnalysis.ts +0 -1
- package/recipes/image-analysis/recipe.json +15 -55
- package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/app/analysis-options-screen.tsx +304 -0
- package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/app/camera.tsx +221 -0
- package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/app/image-capture-screen.tsx +333 -0
- package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/app/loading-screen.tsx +214 -0
- package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/app/loading.tsx +191 -0
- package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/app/results.tsx +137 -0
- package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/app/trait-details.tsx +172 -0
- package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/app/use-analysis-data.ts +160 -0
- package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/app/use-results-screen.ts +151 -0
- package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/components/results/achievement-badge.tsx +77 -0
- package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/components/results/achievement-card.tsx +75 -0
- package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/components/results/achievement-unlocked-modal.tsx +162 -0
- package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/components/results/achievements-section.tsx +44 -0
- package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/components/results/advice-list.tsx +42 -0
- package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/components/results/circular-progress.tsx +233 -0
- package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/components/results/content-card.tsx +38 -0
- package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/components/results/error-state.tsx +42 -0
- package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/components/results/index.ts +9 -0
- package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/components/results/loading-state.tsx +26 -0
- package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/components/results/profile-image.tsx +60 -0
- package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/components/results/results-header.tsx +62 -0
- package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/components/results/score-display.tsx +54 -0
- package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/components/results/share-options-modal.tsx +110 -0
- package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/components/results/traits-grid.tsx +74 -0
- package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/config/analysis-config.ts +80 -0
- package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/config/master-analysis-config.ts +157 -0
- package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/hooks/index.ts +1 -0
- package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/hooks/use-analysis.ts +38 -0
- package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/hooks/use-image-analysis.ts +208 -0
- package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/services/analysis-service.ts +262 -0
- package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/services/share-service.ts +176 -0
- package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/services/trait-details-service.ts +289 -0
- package/recipes/image-analysis-supabase/packages/backend/src/services/image-analyses.ts +132 -0
- package/recipes/image-analysis-supabase/packages/backend/supabase/functions/analyze-image/index.ts +312 -0
- package/recipes/image-analysis-supabase/packages/backend/supabase/migrations/image_analysis.sql +42 -0
- package/recipes/image-analysis-supabase/recipe.json +90 -0
- package/recipes/image-analysis-supabase@latest.zip +0 -0
- package/recipes/image-analysis@latest.zip +0 -0
- package/recipes/image-generator/apps/native/src/features/image-generator/app/index.tsx +16 -2
- package/recipes/image-generator/apps/native/src/features/image-generator/components/image-model-selector.tsx +11 -5
- package/recipes/image-generator/apps/native/src/features/image-generator/hooks/use-image-generator.ts +11 -5
- package/recipes/image-generator/packages/backend/convex/imageGeneration/index.ts +2 -2
- package/recipes/image-generator/recipe.json +16 -39
- package/recipes/image-generator-supabase/apps/native/src/features/image-generator/app/_layout.tsx +26 -0
- package/recipes/image-generator-supabase/apps/native/src/features/image-generator/app/gallery.tsx +217 -0
- package/recipes/image-generator-supabase/apps/native/src/features/image-generator/app/index.tsx +251 -0
- package/recipes/image-generator-supabase/apps/native/src/features/image-generator/components/gallery-image.tsx +25 -0
- package/recipes/image-generator-supabase/apps/native/src/features/image-generator/components/image-detail-modal.tsx +215 -0
- package/recipes/image-generator-supabase/apps/native/src/features/image-generator/components/image-model-selector.tsx +216 -0
- package/recipes/image-generator-supabase/apps/native/src/features/image-generator/components/image-placeholder.tsx +26 -0
- package/recipes/image-generator-supabase/apps/native/src/features/image-generator/hooks/use-image-gallery.ts +71 -0
- package/recipes/image-generator-supabase/apps/native/src/features/image-generator/hooks/use-image-generator-settings.ts +152 -0
- package/recipes/image-generator-supabase/apps/native/src/features/image-generator/hooks/use-image-generator.ts +103 -0
- package/recipes/image-generator-supabase/apps/native/src/features/image-generator/models/models.ts +66 -0
- package/recipes/image-generator-supabase/apps/native/src/features/image-generator/services/image-gallery-service.ts +96 -0
- package/recipes/image-generator-supabase/apps/native/src/features/image-generator/services/image-save-service.ts +120 -0
- package/recipes/image-generator-supabase/packages/backend/supabase/functions/generate-image/index.ts +291 -0
- package/recipes/image-generator-supabase/packages/backend/supabase/migrations/image_generator.sql +71 -0
- package/recipes/image-generator-supabase/recipe.json +86 -0
- package/recipes/image-generator-supabase@latest.zip +0 -0
- package/recipes/image-generator@latest.zip +0 -0
- package/recipes/ios-widget/recipe.json +15 -24
- package/recipes/ios-widget@latest.zip +0 -0
- package/recipes/onboarding/apps/native/src/features/onboarding/analytics/index.ts +9 -0
- package/recipes/onboarding/apps/native/src/features/onboarding/components/onboarding-with-analytics.tsx +141 -0
- package/recipes/onboarding/apps/native/src/features/onboarding/components/onboarding.tsx +173 -0
- package/recipes/onboarding/apps/native/src/features/onboarding/config/onboarding-flow-config.ts +189 -0
- package/recipes/onboarding/apps/native/src/features/onboarding/demo-one/app/index.tsx +42 -0
- package/recipes/onboarding/apps/native/src/features/onboarding/demo-one/data.ts +32 -0
- package/recipes/onboarding/apps/native/src/features/onboarding/expense-tracker/app/index.tsx +43 -0
- package/recipes/onboarding/apps/native/src/features/onboarding/expense-tracker/components/interactive-onboarding.tsx +222 -0
- package/recipes/onboarding/apps/native/src/features/onboarding/expense-tracker/components/steps/ai-tone-step.tsx +133 -0
- package/recipes/onboarding/apps/native/src/features/onboarding/expense-tracker/components/steps/currency-step.tsx +165 -0
- package/recipes/onboarding/apps/native/src/features/onboarding/expense-tracker/components/steps/feature-ai-step.tsx +199 -0
- package/recipes/onboarding/apps/native/src/features/onboarding/expense-tracker/components/steps/feature-chatbot-step.tsx +154 -0
- package/recipes/onboarding/apps/native/src/features/onboarding/expense-tracker/components/steps/feature-manual-step.tsx +156 -0
- package/recipes/onboarding/apps/native/src/features/onboarding/expense-tracker/components/steps/feature-scan-step.tsx +158 -0
- package/recipes/onboarding/apps/native/src/features/onboarding/expense-tracker/components/steps/main-reason-step.tsx +139 -0
- package/recipes/onboarding/apps/native/src/features/onboarding/expense-tracker/components/steps/notification-step.tsx +129 -0
- package/recipes/onboarding/apps/native/src/features/onboarding/expense-tracker/components/steps/overspend-step.tsx +138 -0
- package/recipes/onboarding/apps/native/src/features/onboarding/expense-tracker/components/steps/personalizing-step.tsx +190 -0
- package/recipes/onboarding/apps/native/src/features/onboarding/expense-tracker/components/steps/rating-step.tsx +98 -0
- package/recipes/onboarding/apps/native/src/features/onboarding/expense-tracker/components/steps/reminder-step.tsx +181 -0
- package/recipes/onboarding/apps/native/src/features/onboarding/expense-tracker/components/steps/safety-step.tsx +110 -0
- package/recipes/onboarding/apps/native/src/features/onboarding/expense-tracker/components/steps/struggle-step.tsx +139 -0
- package/recipes/onboarding/apps/native/src/features/onboarding/expense-tracker/components/steps/welcome-step.tsx +217 -0
- package/recipes/onboarding/apps/native/src/features/onboarding/expense-tracker/components/ui/onboarding-header.tsx +58 -0
- package/recipes/onboarding/apps/native/src/features/onboarding/expense-tracker/constants.ts +179 -0
- package/recipes/onboarding/apps/native/src/features/onboarding/hooks/use-onboarding-analytics.ts +323 -0
- package/recipes/onboarding/apps/native/src/features/onboarding/services/onboarding-analytics.ts +432 -0
- package/recipes/onboarding/recipe.json +15 -0
- package/recipes/onboarding@latest.zip +0 -0
- package/recipes/payments/recipe.json +28 -61
- package/recipes/payments-supabase/apps/native/src/features/payments/README.md +200 -0
- package/recipes/payments-supabase/apps/native/src/features/payments/app/local-paywall.tsx +194 -0
- package/recipes/payments-supabase/apps/native/src/features/payments/app/remote-paywall.tsx +79 -0
- package/recipes/payments-supabase/apps/native/src/features/payments/components/payment-initializer.tsx +95 -0
- package/recipes/payments-supabase/apps/native/src/features/payments/components/paywall-error-state.tsx +60 -0
- package/recipes/payments-supabase/apps/native/src/features/payments/components/paywall-local-mode.tsx +116 -0
- package/recipes/payments-supabase/apps/native/src/features/payments/components/paywall-product-card.tsx +133 -0
- package/recipes/payments-supabase/apps/native/src/features/payments/components/paywall-remote-mode.tsx +146 -0
- package/recipes/payments-supabase/apps/native/src/features/payments/hooks/use-entitlement.ts +63 -0
- package/recipes/payments-supabase/apps/native/src/features/payments/index.ts +8 -0
- package/recipes/payments-supabase/apps/native/src/features/payments/services/revenuecat-adapter.ts +407 -0
- package/recipes/payments-supabase/packages/backend/src/services/payments.ts +201 -0
- package/recipes/payments-supabase/packages/backend/supabase/migrations/payments.sql +35 -0
- package/recipes/payments-supabase/recipe.json +72 -0
- package/recipes/payments-supabase@latest.zip +0 -0
- package/recipes/payments@latest.zip +0 -0
- package/recipes/quiz/apps/native/src/features/quiz/index.tsx +1 -2
- package/recipes/quiz/recipe.json +6 -9
- package/recipes/quiz@latest.zip +0 -0
- package/recipes/tracker-app/apps/native/src/features/tracker-app/app/index.tsx +1 -2
- package/recipes/tracker-app/recipe.json +7 -10
- package/recipes/tracker-app@latest.zip +0 -0
- package/recipes/voice-bot/recipe.json +8 -68
- package/recipes/voice-bot.zip +0 -0
- package/recipes/voice-bot@latest.zip +0 -0
- package/recipes/wake-word/recipe.json +10 -9
- package/recipes/wake-word.zip +0 -0
- package/recipes/wake-word@latest.zip +0 -0
- package/recipes/charts/apps/native/src/app/(root)/(protected)/charts/index.tsx +0 -3
- package/recipes/chatbot/packages/backend/convex/lib/rateLimit.ts +0 -100
- package/recipes/chatbot/packages/backend/convex/lib/telemetry.ts +0 -29
- package/recipes/chatbot/packages/backend/convex/ragKnowledge.ts +0 -0
- package/recipes/image-analysis/apps/native/assets/features/image-analyzer/front.jpg +0 -0
- package/recipes/image-analysis/apps/native/assets/features/image-analyzer/side.jpg +0 -0
- package/recipes/image-analysis/apps/native/assets/features/image-analyzer/threeQuarter.jpg +0 -0
- package/recipes/image-analysis/apps/native/src/app/(root)/(protected)/analysis/[type]/_layout.tsx +0 -5
- package/recipes/image-analysis/apps/native/src/app/(root)/(protected)/analysis/[type]/analysis-options.tsx +0 -50
- package/recipes/image-analysis/apps/native/src/app/(root)/(protected)/analysis/[type]/camera.tsx +0 -2
- package/recipes/image-analysis/apps/native/src/app/(root)/(protected)/analysis/[type]/index.tsx +0 -50
- package/recipes/image-analysis/apps/native/src/app/(root)/(protected)/analysis/[type]/loading.tsx +0 -50
- package/recipes/image-analysis/apps/native/src/app/(root)/(protected)/analysis/[type]/results.tsx +0 -2
- package/recipes/image-analysis/apps/native/src/app/(root)/(protected)/analysis/[type]/trait-details.tsx +0 -3
- package/recipes/image-analysis/packages/backend/convex/imageAnalysisFunctions.ts +0 -325
- package/recipes/image-analysis/packages/backend/convex/lib/ai/imageAnalysisAdapter.ts +0 -200
- package/recipes/payments/apps/native/src/app/(root)/(protected)/paywall/index.tsx +0 -74
- package/recipes/payments/apps/native/src/app/(root)/(protected)/paywall/local.tsx +0 -25
- package/recipes/payments/apps/native/src/app/(root)/(protected)/paywall/remote.tsx +0 -23
- package/recipes/quiz/apps/native/src/app/(root)/(protected)/quiz/index.tsx +0 -47
- package/recipes/tracker-app/apps/native/src/app/(root)/(protected)/tracker-app/index.tsx +0 -1
- package/recipes/voice-bot/apps/native/src/app/(root)/(protected)/voice-bot/index.tsx +0 -27
- package/recipes/voice-bot/packages/backend/convex/router.ts +0 -81
- /package/recipes/{chatbot/apps/native/src/app/(root)/(protected) → chatbot-supabase/apps/native/src/app}/chatbot/index.tsx +0 -0
- /package/recipes/{image-generator/apps/native/src/app/(root)/(protected) → image-generator-supabase/apps/native/src/app}/image-generator/gallery.tsx +0 -0
- /package/recipes/{image-generator/apps/native/src/app/(root)/(protected) → image-generator-supabase/apps/native/src/app}/image-generator/index.tsx +0 -0
|
@@ -0,0 +1,369 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Recordings Service
|
|
3
|
+
*
|
|
4
|
+
* Handles recording operations including createRecordingDraft, finalizeRecording,
|
|
5
|
+
* listRecordings, updateRecordingName, and deleteRecording.
|
|
6
|
+
*
|
|
7
|
+
* Requirements: 12.1, 12.2, 12.3, 12.4, 12.5
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import type { SupabaseClientType } from '../lib/supabase';
|
|
11
|
+
import type { Recording } from '../types';
|
|
12
|
+
import type { Json } from '../types/database';
|
|
13
|
+
|
|
14
|
+
export interface CreateRecordingDraftInput {
|
|
15
|
+
name?: string | null;
|
|
16
|
+
fileUri: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface CreateRecordingDraftResult {
|
|
20
|
+
success: boolean;
|
|
21
|
+
recording?: Recording;
|
|
22
|
+
error?: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface FinalizeRecordingInput {
|
|
26
|
+
recordingId: string;
|
|
27
|
+
duration: number;
|
|
28
|
+
fileUri: string;
|
|
29
|
+
metering?: Json | null;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface FinalizeRecordingResult {
|
|
33
|
+
success: boolean;
|
|
34
|
+
recording?: Recording;
|
|
35
|
+
error?: string;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface ListRecordingsResult {
|
|
39
|
+
success: boolean;
|
|
40
|
+
recordings?: Recording[];
|
|
41
|
+
hasMore?: boolean;
|
|
42
|
+
error?: string;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export interface UpdateRecordingNameResult {
|
|
46
|
+
success: boolean;
|
|
47
|
+
recording?: Recording;
|
|
48
|
+
error?: string;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export interface DeleteRecordingResult {
|
|
52
|
+
success: boolean;
|
|
53
|
+
error?: string;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const DEFAULT_PAGE_SIZE = 50;
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Create a recording draft
|
|
60
|
+
*
|
|
61
|
+
* Creates a new recording with status "draft". The recording can be finalized
|
|
62
|
+
* later with duration and metering data.
|
|
63
|
+
*
|
|
64
|
+
* @param supabase - Supabase client (authenticated)
|
|
65
|
+
* @param input - Recording draft data
|
|
66
|
+
* @returns The created recording draft
|
|
67
|
+
*
|
|
68
|
+
* Requirements: 12.1
|
|
69
|
+
*/
|
|
70
|
+
export async function createRecordingDraft(
|
|
71
|
+
supabase: SupabaseClientType,
|
|
72
|
+
input: CreateRecordingDraftInput
|
|
73
|
+
): Promise<CreateRecordingDraftResult> {
|
|
74
|
+
// Get the current auth user
|
|
75
|
+
const { data: authData, error: authError } = await supabase.auth.getUser();
|
|
76
|
+
|
|
77
|
+
if (authError || !authData.user) {
|
|
78
|
+
return { success: false, error: 'Not authenticated' };
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (!input.fileUri) {
|
|
82
|
+
return { success: false, error: 'File URI is required' };
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Create the recording draft
|
|
86
|
+
const { data: recording, error: createError } = await supabase
|
|
87
|
+
.from('recordings')
|
|
88
|
+
.insert({
|
|
89
|
+
user_id: authData.user.id,
|
|
90
|
+
name: input.name || null,
|
|
91
|
+
file_uri: input.fileUri,
|
|
92
|
+
duration: 0, // Will be updated when finalized
|
|
93
|
+
status: 'draft',
|
|
94
|
+
metering: null,
|
|
95
|
+
})
|
|
96
|
+
.select()
|
|
97
|
+
.single();
|
|
98
|
+
|
|
99
|
+
if (createError) {
|
|
100
|
+
return { success: false, error: `Failed to create recording draft: ${createError.message}` };
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return { success: true, recording };
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Finalize a recording
|
|
109
|
+
*
|
|
110
|
+
* Updates a draft recording with duration, file URI, and metering data,
|
|
111
|
+
* then sets status to "completed".
|
|
112
|
+
*
|
|
113
|
+
* @param supabase - Supabase client (authenticated)
|
|
114
|
+
* @param input - Finalization data
|
|
115
|
+
* @returns The finalized recording
|
|
116
|
+
*
|
|
117
|
+
* Requirements: 12.2
|
|
118
|
+
*/
|
|
119
|
+
export async function finalizeRecording(
|
|
120
|
+
supabase: SupabaseClientType,
|
|
121
|
+
input: FinalizeRecordingInput
|
|
122
|
+
): Promise<FinalizeRecordingResult> {
|
|
123
|
+
// Get the current auth user
|
|
124
|
+
const { data: authData, error: authError } = await supabase.auth.getUser();
|
|
125
|
+
|
|
126
|
+
if (authError || !authData.user) {
|
|
127
|
+
return { success: false, error: 'Not authenticated' };
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if (!input.recordingId) {
|
|
131
|
+
return { success: false, error: 'Recording ID is required' };
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if (input.duration < 0) {
|
|
135
|
+
return { success: false, error: 'Duration must be non-negative' };
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if (!input.fileUri) {
|
|
139
|
+
return { success: false, error: 'File URI is required' };
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Update the recording (RLS + user_id check ensures ownership)
|
|
143
|
+
const { data: recording, error: updateError } = await supabase
|
|
144
|
+
.from('recordings')
|
|
145
|
+
.update({
|
|
146
|
+
duration: input.duration,
|
|
147
|
+
file_uri: input.fileUri,
|
|
148
|
+
metering: input.metering || null,
|
|
149
|
+
status: 'completed',
|
|
150
|
+
})
|
|
151
|
+
.eq('id', input.recordingId)
|
|
152
|
+
.eq('user_id', authData.user.id)
|
|
153
|
+
.eq('status', 'draft') // Can only finalize drafts
|
|
154
|
+
.select()
|
|
155
|
+
.single();
|
|
156
|
+
|
|
157
|
+
if (updateError) {
|
|
158
|
+
if (updateError.code === 'PGRST116') {
|
|
159
|
+
return { success: false, error: 'Recording not found, not owned by user, or already finalized' };
|
|
160
|
+
}
|
|
161
|
+
return { success: false, error: `Failed to finalize recording: ${updateError.message}` };
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
return { success: true, recording };
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* List recordings for the current user
|
|
169
|
+
*
|
|
170
|
+
* Returns only completed recordings, ordered by creation time (newest first).
|
|
171
|
+
*
|
|
172
|
+
* @param supabase - Supabase client (authenticated)
|
|
173
|
+
* @param options - Pagination options
|
|
174
|
+
* @returns Paginated list of completed recordings
|
|
175
|
+
*
|
|
176
|
+
* Requirements: 12.3
|
|
177
|
+
*/
|
|
178
|
+
export async function listRecordings(
|
|
179
|
+
supabase: SupabaseClientType,
|
|
180
|
+
options?: {
|
|
181
|
+
limit?: number;
|
|
182
|
+
cursor?: string; // created_at timestamp for cursor-based pagination
|
|
183
|
+
}
|
|
184
|
+
): Promise<ListRecordingsResult> {
|
|
185
|
+
// Get the current auth user
|
|
186
|
+
const { data: authData, error: authError } = await supabase.auth.getUser();
|
|
187
|
+
|
|
188
|
+
if (authError || !authData.user) {
|
|
189
|
+
return { success: false, error: 'Not authenticated' };
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
const limit = options?.limit || DEFAULT_PAGE_SIZE;
|
|
193
|
+
|
|
194
|
+
// Build query - only return completed recordings
|
|
195
|
+
let query = supabase
|
|
196
|
+
.from('recordings')
|
|
197
|
+
.select('*')
|
|
198
|
+
.eq('user_id', authData.user.id)
|
|
199
|
+
.eq('status', 'completed')
|
|
200
|
+
.order('created_at', { ascending: false })
|
|
201
|
+
.limit(limit + 1); // Fetch one extra to check if there are more
|
|
202
|
+
|
|
203
|
+
// Apply cursor if provided
|
|
204
|
+
if (options?.cursor) {
|
|
205
|
+
query = query.lt('created_at', options.cursor);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
const { data: recordings, error: listError } = await query;
|
|
209
|
+
|
|
210
|
+
if (listError) {
|
|
211
|
+
return { success: false, error: `Failed to list recordings: ${listError.message}` };
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// Check if there are more results
|
|
215
|
+
const hasMore = recordings && recordings.length > limit;
|
|
216
|
+
const resultRecordings = hasMore ? recordings.slice(0, limit) : (recordings || []);
|
|
217
|
+
|
|
218
|
+
return {
|
|
219
|
+
success: true,
|
|
220
|
+
recordings: resultRecordings,
|
|
221
|
+
hasMore,
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Update a recording's name
|
|
228
|
+
*
|
|
229
|
+
* Only allows update if the user owns the recording.
|
|
230
|
+
*
|
|
231
|
+
* @param supabase - Supabase client (authenticated)
|
|
232
|
+
* @param recordingId - The ID of the recording to update
|
|
233
|
+
* @param name - The new name for the recording
|
|
234
|
+
* @returns The updated recording
|
|
235
|
+
*
|
|
236
|
+
* Requirements: 12.4
|
|
237
|
+
*/
|
|
238
|
+
export async function updateRecordingName(
|
|
239
|
+
supabase: SupabaseClientType,
|
|
240
|
+
recordingId: string,
|
|
241
|
+
name: string | null
|
|
242
|
+
): Promise<UpdateRecordingNameResult> {
|
|
243
|
+
// Get the current auth user
|
|
244
|
+
const { data: authData, error: authError } = await supabase.auth.getUser();
|
|
245
|
+
|
|
246
|
+
if (authError || !authData.user) {
|
|
247
|
+
return { success: false, error: 'Not authenticated' };
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
if (!recordingId) {
|
|
251
|
+
return { success: false, error: 'Recording ID is required' };
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// Update the recording name (RLS + user_id check ensures ownership)
|
|
255
|
+
const { data: recording, error: updateError } = await supabase
|
|
256
|
+
.from('recordings')
|
|
257
|
+
.update({ name })
|
|
258
|
+
.eq('id', recordingId)
|
|
259
|
+
.eq('user_id', authData.user.id)
|
|
260
|
+
.select()
|
|
261
|
+
.single();
|
|
262
|
+
|
|
263
|
+
if (updateError) {
|
|
264
|
+
if (updateError.code === 'PGRST116') {
|
|
265
|
+
return { success: false, error: 'Recording not found or access denied' };
|
|
266
|
+
}
|
|
267
|
+
return { success: false, error: `Failed to update recording: ${updateError.message}` };
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
return { success: true, recording };
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* Delete a recording
|
|
275
|
+
*
|
|
276
|
+
* Removes the recording record. Note: This does not delete the associated
|
|
277
|
+
* file from storage - that should be handled separately.
|
|
278
|
+
*
|
|
279
|
+
* @param supabase - Supabase client (authenticated)
|
|
280
|
+
* @param recordingId - The ID of the recording to delete
|
|
281
|
+
* @returns Success or error
|
|
282
|
+
*
|
|
283
|
+
* Requirements: 12.5
|
|
284
|
+
*/
|
|
285
|
+
export async function deleteRecording(
|
|
286
|
+
supabase: SupabaseClientType,
|
|
287
|
+
recordingId: string
|
|
288
|
+
): Promise<DeleteRecordingResult> {
|
|
289
|
+
// Get the current auth user
|
|
290
|
+
const { data: authData, error: authError } = await supabase.auth.getUser();
|
|
291
|
+
|
|
292
|
+
if (authError || !authData.user) {
|
|
293
|
+
return { success: false, error: 'Not authenticated' };
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
if (!recordingId) {
|
|
297
|
+
return { success: false, error: 'Recording ID is required' };
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
// Delete the recording (RLS + user_id check ensures ownership)
|
|
301
|
+
const { data, error: deleteError } = await supabase
|
|
302
|
+
.from('recordings')
|
|
303
|
+
.delete()
|
|
304
|
+
.eq('id', recordingId)
|
|
305
|
+
.eq('user_id', authData.user.id)
|
|
306
|
+
.select('id')
|
|
307
|
+
.single();
|
|
308
|
+
|
|
309
|
+
if (deleteError) {
|
|
310
|
+
if (deleteError.code === 'PGRST116') {
|
|
311
|
+
return { success: false, error: 'Recording not found or access denied' };
|
|
312
|
+
}
|
|
313
|
+
return { success: false, error: `Failed to delete recording: ${deleteError.message}` };
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
if (!data) {
|
|
317
|
+
return { success: false, error: 'Recording not found or access denied' };
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
return { success: true };
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
/**
|
|
324
|
+
* Get a recording by ID
|
|
325
|
+
*
|
|
326
|
+
* @param supabase - Supabase client (authenticated)
|
|
327
|
+
* @param recordingId - The ID of the recording
|
|
328
|
+
* @returns The recording or error
|
|
329
|
+
*/
|
|
330
|
+
export async function getRecordingById(
|
|
331
|
+
supabase: SupabaseClientType,
|
|
332
|
+
recordingId: string
|
|
333
|
+
): Promise<{ success: boolean; recording?: Recording; error?: string }> {
|
|
334
|
+
// Get the current auth user
|
|
335
|
+
const { data: authData, error: authError } = await supabase.auth.getUser();
|
|
336
|
+
|
|
337
|
+
if (authError || !authData.user) {
|
|
338
|
+
return { success: false, error: 'Not authenticated' };
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
if (!recordingId) {
|
|
342
|
+
return { success: false, error: 'Recording ID is required' };
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
const { data: recording, error: getError } = await supabase
|
|
346
|
+
.from('recordings')
|
|
347
|
+
.select('*')
|
|
348
|
+
.eq('id', recordingId)
|
|
349
|
+
.eq('user_id', authData.user.id)
|
|
350
|
+
.single();
|
|
351
|
+
|
|
352
|
+
if (getError) {
|
|
353
|
+
if (getError.code === 'PGRST116') {
|
|
354
|
+
return { success: false, error: 'Recording not found' };
|
|
355
|
+
}
|
|
356
|
+
return { success: false, error: `Failed to get recording: ${getError.message}` };
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
return { success: true, recording };
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
export const recordingsService = {
|
|
363
|
+
createRecordingDraft,
|
|
364
|
+
finalizeRecording,
|
|
365
|
+
listRecordings,
|
|
366
|
+
updateRecordingName,
|
|
367
|
+
deleteRecording,
|
|
368
|
+
getRecordingById,
|
|
369
|
+
};
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
-- Audio Recorder Feature Migration
|
|
2
|
+
-- Creates tables for recording metadata and storage
|
|
3
|
+
-- Run this migration when adding the audio-recorder feature via CLI
|
|
4
|
+
|
|
5
|
+
-- ============================================================================
|
|
6
|
+
-- RECORDINGS TABLE
|
|
7
|
+
-- Stores audio recording metadata
|
|
8
|
+
-- ============================================================================
|
|
9
|
+
CREATE TABLE IF NOT EXISTS recordings (
|
|
10
|
+
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
11
|
+
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
|
12
|
+
name TEXT,
|
|
13
|
+
file_uri TEXT NOT NULL,
|
|
14
|
+
duration INTEGER NOT NULL,
|
|
15
|
+
status TEXT NOT NULL CHECK (status IN ('draft', 'completed')),
|
|
16
|
+
metering JSONB,
|
|
17
|
+
created_at TIMESTAMPTZ DEFAULT now()
|
|
18
|
+
);
|
|
19
|
+
|
|
20
|
+
-- ============================================================================
|
|
21
|
+
-- INDEXES
|
|
22
|
+
-- ============================================================================
|
|
23
|
+
CREATE INDEX IF NOT EXISTS idx_recordings_user_id_status ON recordings(user_id, status);
|
|
24
|
+
|
|
25
|
+
-- ============================================================================
|
|
26
|
+
-- RLS POLICIES
|
|
27
|
+
-- ============================================================================
|
|
28
|
+
ALTER TABLE recordings ENABLE ROW LEVEL SECURITY;
|
|
29
|
+
|
|
30
|
+
CREATE POLICY "recordings_select_own" ON recordings
|
|
31
|
+
FOR SELECT USING (auth.uid() = user_id);
|
|
32
|
+
|
|
33
|
+
CREATE POLICY "recordings_insert_own" ON recordings
|
|
34
|
+
FOR INSERT WITH CHECK (auth.uid() = user_id);
|
|
35
|
+
|
|
36
|
+
CREATE POLICY "recordings_update_own" ON recordings
|
|
37
|
+
FOR UPDATE USING (auth.uid() = user_id);
|
|
38
|
+
|
|
39
|
+
CREATE POLICY "recordings_delete_own" ON recordings
|
|
40
|
+
FOR DELETE USING (auth.uid() = user_id);
|
|
41
|
+
|
|
42
|
+
-- ============================================================================
|
|
43
|
+
-- STORAGE BUCKET - recordings
|
|
44
|
+
-- ============================================================================
|
|
45
|
+
INSERT INTO storage.buckets (id, name, public, file_size_limit, allowed_mime_types)
|
|
46
|
+
VALUES (
|
|
47
|
+
'recordings',
|
|
48
|
+
'recordings',
|
|
49
|
+
false,
|
|
50
|
+
104857600, -- 100MB limit
|
|
51
|
+
ARRAY['audio/mpeg', 'audio/wav', 'audio/m4a', 'audio/aac', 'audio/ogg']
|
|
52
|
+
) ON CONFLICT (id) DO NOTHING;
|
|
53
|
+
|
|
54
|
+
-- Storage RLS for recordings
|
|
55
|
+
CREATE POLICY "Users can view their own recordings"
|
|
56
|
+
ON storage.objects FOR SELECT TO authenticated
|
|
57
|
+
USING (bucket_id = 'recordings' AND (storage.foldername(name))[1] = auth.uid()::text);
|
|
58
|
+
|
|
59
|
+
CREATE POLICY "Users can upload their own recordings"
|
|
60
|
+
ON storage.objects FOR INSERT TO authenticated
|
|
61
|
+
WITH CHECK (bucket_id = 'recordings' AND (storage.foldername(name))[1] = auth.uid()::text);
|
|
62
|
+
|
|
63
|
+
CREATE POLICY "Users can update their own recordings"
|
|
64
|
+
ON storage.objects FOR UPDATE TO authenticated
|
|
65
|
+
USING (bucket_id = 'recordings' AND (storage.foldername(name))[1] = auth.uid()::text)
|
|
66
|
+
WITH CHECK (bucket_id = 'recordings' AND (storage.foldername(name))[1] = auth.uid()::text);
|
|
67
|
+
|
|
68
|
+
CREATE POLICY "Users can delete their own recordings"
|
|
69
|
+
ON storage.objects FOR DELETE TO authenticated
|
|
70
|
+
USING (bucket_id = 'recordings' AND (storage.foldername(name))[1] = auth.uid()::text);
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "audio-recorder",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Audio recording with Supabase storage",
|
|
5
|
+
"copy": [
|
|
6
|
+
{
|
|
7
|
+
"from": "apps/native/src/features/audio-recorder",
|
|
8
|
+
"to": "apps/native/src/features/audio-recorder"
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
"from": "apps/native/src/api-client/supabase/recordings.ts",
|
|
12
|
+
"to": "apps/native/src/api-client/supabase/recordings.ts"
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
"from": "packages/backend/supabase/migrations/recordings.sql",
|
|
16
|
+
"to": "packages/backend/supabase/migrations/recordings.sql"
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
"from": "packages/backend/src/services/recordings.ts",
|
|
20
|
+
"to": "packages/backend/src/services/recordings.ts"
|
|
21
|
+
}
|
|
22
|
+
],
|
|
23
|
+
"nav": {
|
|
24
|
+
"href": "/(root)/(protected)/audio-recorder",
|
|
25
|
+
"label": "Audio Recorder",
|
|
26
|
+
"icon": "🎙️",
|
|
27
|
+
"color": "#EF4444"
|
|
28
|
+
},
|
|
29
|
+
"target": "native",
|
|
30
|
+
"configuration": {
|
|
31
|
+
"apiClient": {
|
|
32
|
+
"exports": [
|
|
33
|
+
"export { type SupabaseRecordingsApi as RecordingsApi, supabaseRecordingsApi as recordingsApi } from './supabase/recordings';"
|
|
34
|
+
]
|
|
35
|
+
},
|
|
36
|
+
"backend": {
|
|
37
|
+
"exports": [
|
|
38
|
+
"export { recordingsService, createRecordingDraft, finalizeRecording, listRecordings, updateRecordingName, deleteRecording, getRecordingById } from './services/recordings';",
|
|
39
|
+
"export type { CreateRecordingDraftInput, CreateRecordingDraftResult, FinalizeRecordingInput, FinalizeRecordingResult, ListRecordingsResult, UpdateRecordingNameResult, DeleteRecordingResult } from './services/recordings';"
|
|
40
|
+
]
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
"dependencies": {
|
|
44
|
+
"expo": ["@supabase/supabase-js", "expo-av", "expo-file-system"]
|
|
45
|
+
},
|
|
46
|
+
"manualSteps": [
|
|
47
|
+
{
|
|
48
|
+
"title": "Apply Supabase migration",
|
|
49
|
+
"description": "Run: cd packages/backend && supabase db push"
|
|
50
|
+
}
|
|
51
|
+
]
|
|
52
|
+
}
|
|
Binary file
|
|
Binary file
|
|
@@ -27,7 +27,7 @@ type Props = {
|
|
|
27
27
|
};
|
|
28
28
|
|
|
29
29
|
export const BarChart = ({ data, config = {}, style }: Props) => {
|
|
30
|
-
const [containerWidth, setContainerWidth] = useState(300);
|
|
30
|
+
const [containerWidth, setContainerWidth] = useState(() => config.width ?? 300);
|
|
31
31
|
const {
|
|
32
32
|
height = 200,
|
|
33
33
|
padding = 20,
|
|
@@ -36,7 +36,7 @@ export const BarChart = ({ data, config = {}, style }: Props) => {
|
|
|
36
36
|
showLabels = true,
|
|
37
37
|
} = config;
|
|
38
38
|
|
|
39
|
-
const chartWidth =
|
|
39
|
+
const chartWidth = config.width ?? containerWidth;
|
|
40
40
|
|
|
41
41
|
const theme = useThemeConfig();
|
|
42
42
|
const primaryColor = theme.colors.primary as string;
|
|
@@ -71,7 +71,7 @@ export const BarChart = ({ data, config = {}, style }: Props) => {
|
|
|
71
71
|
|
|
72
72
|
if (!data.length) return null;
|
|
73
73
|
|
|
74
|
-
const maxValue = Math.max(...data.map((d) => d.value));
|
|
74
|
+
const maxValue = Math.max(1, ...data.map((d) => d.value));
|
|
75
75
|
const innerChartWidth = chartWidth - padding * 2;
|
|
76
76
|
const chartHeight = height - padding * 2;
|
|
77
77
|
const barWidth = (innerChartWidth / data.length) * 0.8;
|
|
@@ -36,7 +36,7 @@ type Props = {
|
|
|
36
36
|
};
|
|
37
37
|
|
|
38
38
|
export const CandlestickChart = ({ data, config = {}, style }: Props) => {
|
|
39
|
-
const [containerWidth, setContainerWidth] = useState(300);
|
|
39
|
+
const [containerWidth, setContainerWidth] = useState(() => config.width ?? 300);
|
|
40
40
|
|
|
41
41
|
const {
|
|
42
42
|
height = 200,
|
|
@@ -47,7 +47,7 @@ export const CandlestickChart = ({ data, config = {}, style }: Props) => {
|
|
|
47
47
|
duration = 800,
|
|
48
48
|
} = config;
|
|
49
49
|
|
|
50
|
-
const chartWidth =
|
|
50
|
+
const chartWidth = config.width ?? containerWidth;
|
|
51
51
|
|
|
52
52
|
const theme = useThemeConfig();
|
|
53
53
|
const bullishColor = '#10B981';
|
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
import type React from 'react';
|
|
2
|
-
import {
|
|
2
|
+
import { useWindowDimensions, View } from 'react-native';
|
|
3
3
|
|
|
4
4
|
import { Text } from '@/components/ui';
|
|
5
5
|
import { useThemeConfig } from '@/lib/use-theme-config';
|
|
6
6
|
|
|
7
|
-
const { width: screenWidth } = Dimensions.get('window');
|
|
8
|
-
|
|
9
7
|
interface ChartCardProps {
|
|
10
8
|
title: string;
|
|
11
9
|
subtitle?: string;
|
|
@@ -17,17 +15,19 @@ interface ChartCardProps {
|
|
|
17
15
|
const ChartCard: React.FC<ChartCardProps> = ({
|
|
18
16
|
title,
|
|
19
17
|
subtitle,
|
|
20
|
-
width
|
|
18
|
+
width,
|
|
21
19
|
height: _height,
|
|
22
20
|
children,
|
|
23
21
|
}) => {
|
|
24
22
|
const theme = useThemeConfig();
|
|
23
|
+
const { width: screenWidth } = useWindowDimensions();
|
|
24
|
+
const cardWidth = width ?? screenWidth - 32;
|
|
25
25
|
|
|
26
26
|
return (
|
|
27
27
|
<View
|
|
28
28
|
className="mb-4 overflow-hidden rounded-xl"
|
|
29
29
|
style={{
|
|
30
|
-
width,
|
|
30
|
+
width: cardWidth,
|
|
31
31
|
backgroundColor: theme.colors.card,
|
|
32
32
|
borderColor: theme.colors.outline,
|
|
33
33
|
borderWidth: 1,
|
|
@@ -27,7 +27,7 @@ type Props = {
|
|
|
27
27
|
};
|
|
28
28
|
|
|
29
29
|
export const ColumnChart = ({ data, config = {}, style }: Props) => {
|
|
30
|
-
const [containerWidth, setContainerWidth] = useState(300);
|
|
30
|
+
const [containerWidth, setContainerWidth] = useState(() => config.width ?? 300);
|
|
31
31
|
const {
|
|
32
32
|
height = 200,
|
|
33
33
|
padding = 20,
|
|
@@ -35,7 +35,7 @@ export const ColumnChart = ({ data, config = {}, style }: Props) => {
|
|
|
35
35
|
animated = true,
|
|
36
36
|
duration = 800,
|
|
37
37
|
} = config;
|
|
38
|
-
const chartWidth =
|
|
38
|
+
const chartWidth = config.width ?? containerWidth;
|
|
39
39
|
|
|
40
40
|
const theme = useThemeConfig();
|
|
41
41
|
const primaryColor = theme.colors.primary as string;
|
|
@@ -70,7 +70,7 @@ export const ColumnChart = ({ data, config = {}, style }: Props) => {
|
|
|
70
70
|
|
|
71
71
|
if (!data.length) return null;
|
|
72
72
|
|
|
73
|
-
const maxValue = Math.max(...data.map((d) => d.value));
|
|
73
|
+
const maxValue = Math.max(1, ...data.map((d) => d.value));
|
|
74
74
|
const innerChartWidth = chartWidth - padding * 2;
|
|
75
75
|
const chartHeight = height - padding * 2;
|
|
76
76
|
const barHeight = (chartHeight / data.length) * 0.8;
|
|
@@ -40,6 +40,21 @@ const makeDonutSlicePath = (
|
|
|
40
40
|
startAngle: number,
|
|
41
41
|
endAngle: number,
|
|
42
42
|
) => {
|
|
43
|
+
if (innerR <= 0.5) {
|
|
44
|
+
const largeArcFlag = endAngle - startAngle > Math.PI ? 1 : 0;
|
|
45
|
+
const x1 = cx + outerR * Math.cos(startAngle);
|
|
46
|
+
const y1 = cy + outerR * Math.sin(startAngle);
|
|
47
|
+
const x2 = cx + outerR * Math.cos(endAngle);
|
|
48
|
+
const y2 = cy + outerR * Math.sin(endAngle);
|
|
49
|
+
const pathData = [
|
|
50
|
+
`M ${cx} ${cy}`,
|
|
51
|
+
`L ${x1} ${y1}`,
|
|
52
|
+
`A ${outerR} ${outerR} 0 ${largeArcFlag} 1 ${x2} ${y2}`,
|
|
53
|
+
'Z',
|
|
54
|
+
].join(' ');
|
|
55
|
+
return Skia.Path.MakeFromSVGString(pathData) || Skia.Path.Make();
|
|
56
|
+
}
|
|
57
|
+
|
|
43
58
|
const largeArcFlag = endAngle - startAngle > Math.PI ? 1 : 0;
|
|
44
59
|
|
|
45
60
|
const x1 = cx + outerR * Math.cos(startAngle);
|
|
@@ -64,7 +79,7 @@ const makeDonutSlicePath = (
|
|
|
64
79
|
};
|
|
65
80
|
|
|
66
81
|
export const DoughnutChart = ({ data, config = {}, style }: Props) => {
|
|
67
|
-
const [containerWidth, setContainerWidth] = useState(300);
|
|
82
|
+
const [containerWidth, setContainerWidth] = useState(() => config.width ?? 300);
|
|
68
83
|
const {
|
|
69
84
|
height = 200,
|
|
70
85
|
animated = true,
|
|
@@ -75,7 +90,7 @@ export const DoughnutChart = ({ data, config = {}, style }: Props) => {
|
|
|
75
90
|
|
|
76
91
|
const theme = useThemeConfig();
|
|
77
92
|
const primaryColor = theme.colors.primary as string;
|
|
78
|
-
const chartWidth =
|
|
93
|
+
const chartWidth = config.width ?? containerWidth;
|
|
79
94
|
const centerX = chartWidth / 2;
|
|
80
95
|
const centerY = height / 2;
|
|
81
96
|
|
|
@@ -83,8 +98,9 @@ export const DoughnutChart = ({ data, config = {}, style }: Props) => {
|
|
|
83
98
|
1,
|
|
84
99
|
data.reduce((s, d) => s + d.value, 0),
|
|
85
100
|
);
|
|
86
|
-
const outerR = Math.min(chartWidth, height) / 2 - 20;
|
|
87
|
-
const
|
|
101
|
+
const outerR = Math.max(1, Math.min(chartWidth, height) / 2 - 20);
|
|
102
|
+
const safeInnerRadius = Math.min(0.95, Math.max(0, innerRadius));
|
|
103
|
+
const innerR = outerR * safeInnerRadius;
|
|
88
104
|
|
|
89
105
|
const [progress, setProgress] = useState(1);
|
|
90
106
|
|