vibefast-cli 1.1.3 → 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/__tests__/recipes.test.js +25 -3
- 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 +547 -543
- 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/{imageAnalysisFunctions.ts → imageAnalysis.ts} +5 -5
- 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/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,189 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Message Handler Service
|
|
3
|
+
*
|
|
4
|
+
* Handles message submission and chat operations with Supabase backend.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { Alert } from 'react-native';
|
|
8
|
+
|
|
9
|
+
import { defaultCacheService } from '@/core/cache';
|
|
10
|
+
import type { AppMessage } from '@/features/chatbot/types';
|
|
11
|
+
import { translate } from '@/lib';
|
|
12
|
+
|
|
13
|
+
export type AppAttachment = {
|
|
14
|
+
storageId: string;
|
|
15
|
+
url?: string;
|
|
16
|
+
mimeType?: string;
|
|
17
|
+
type: 'image';
|
|
18
|
+
fileName?: string;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
type MessageSubmissionOptions = {
|
|
22
|
+
currentInput: string;
|
|
23
|
+
conversationId: string;
|
|
24
|
+
attachmentsFromInputBar?: {
|
|
25
|
+
type: 'image';
|
|
26
|
+
storageId: string;
|
|
27
|
+
fileName?: string;
|
|
28
|
+
mimeType?: string;
|
|
29
|
+
url?: string;
|
|
30
|
+
}[];
|
|
31
|
+
storeUserMessageMutation: {
|
|
32
|
+
mutateAsync: (args: {
|
|
33
|
+
conversationId: string;
|
|
34
|
+
authorType: 'user' | 'bot';
|
|
35
|
+
text?: string;
|
|
36
|
+
attachments?: {
|
|
37
|
+
type: 'image';
|
|
38
|
+
storageId: string;
|
|
39
|
+
fileName?: string;
|
|
40
|
+
mimeType?: string;
|
|
41
|
+
}[];
|
|
42
|
+
}) => Promise<{ id: string } | null>;
|
|
43
|
+
};
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
type ClearChatOptions = {
|
|
47
|
+
conversationId: string;
|
|
48
|
+
clearConversationMutation: {
|
|
49
|
+
mutateAsync: (args: { conversationId: string }) => Promise<any>;
|
|
50
|
+
};
|
|
51
|
+
onCleared?: () => void;
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Service for handling message submission and chat operations
|
|
56
|
+
*/
|
|
57
|
+
export class MessageHandlerService {
|
|
58
|
+
/**
|
|
59
|
+
* Store a user message to the database before sending to AI
|
|
60
|
+
*/
|
|
61
|
+
static async storeUserMessage(
|
|
62
|
+
options: MessageSubmissionOptions,
|
|
63
|
+
): Promise<string | null> {
|
|
64
|
+
const {
|
|
65
|
+
currentInput,
|
|
66
|
+
conversationId,
|
|
67
|
+
attachmentsFromInputBar,
|
|
68
|
+
storeUserMessageMutation,
|
|
69
|
+
} = options;
|
|
70
|
+
|
|
71
|
+
try {
|
|
72
|
+
console.log('[MessageHandlerService] Storing user message to DB...');
|
|
73
|
+
const result = await storeUserMessageMutation.mutateAsync({
|
|
74
|
+
conversationId,
|
|
75
|
+
authorType: 'user',
|
|
76
|
+
text: currentInput.trim() || undefined,
|
|
77
|
+
attachments: attachmentsFromInputBar?.map((att) => ({
|
|
78
|
+
type: 'image',
|
|
79
|
+
storageId: att.storageId,
|
|
80
|
+
fileName: att.fileName,
|
|
81
|
+
mimeType: att.mimeType,
|
|
82
|
+
url: att.url,
|
|
83
|
+
})),
|
|
84
|
+
});
|
|
85
|
+
console.log('[MessageHandlerService] User message stored successfully.');
|
|
86
|
+
return result?.id ?? null;
|
|
87
|
+
} catch (dbError) {
|
|
88
|
+
console.error(
|
|
89
|
+
'[MessageHandlerService] Failed to store user message:',
|
|
90
|
+
dbError,
|
|
91
|
+
);
|
|
92
|
+
Alert.alert(translate('common.error'), translate('chatbot.send_failed'), [
|
|
93
|
+
{
|
|
94
|
+
text: translate('common.ok'),
|
|
95
|
+
onPress: () => {
|
|
96
|
+
// Reset the useChat hook by reloading the data
|
|
97
|
+
defaultCacheService.removeItem(`conversation_${conversationId}`);
|
|
98
|
+
},
|
|
99
|
+
},
|
|
100
|
+
]);
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Handle clearing a chat conversation
|
|
107
|
+
*/
|
|
108
|
+
static clearChat(options: ClearChatOptions): void {
|
|
109
|
+
const { conversationId, clearConversationMutation } = options;
|
|
110
|
+
|
|
111
|
+
Alert.alert(
|
|
112
|
+
'Clear Chat',
|
|
113
|
+
'Are you sure you want to clear all messages? This action cannot be undone.',
|
|
114
|
+
[
|
|
115
|
+
{
|
|
116
|
+
text: 'Cancel',
|
|
117
|
+
style: 'cancel',
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
text: 'Clear',
|
|
121
|
+
style: 'destructive',
|
|
122
|
+
onPress: async () => {
|
|
123
|
+
try {
|
|
124
|
+
console.log(
|
|
125
|
+
'[MessageHandlerService] Clearing conversation:',
|
|
126
|
+
conversationId,
|
|
127
|
+
);
|
|
128
|
+
await clearConversationMutation.mutateAsync({
|
|
129
|
+
conversationId,
|
|
130
|
+
});
|
|
131
|
+
console.log(
|
|
132
|
+
'[MessageHandlerService] Conversation cleared successfully',
|
|
133
|
+
);
|
|
134
|
+
|
|
135
|
+
// Reset the useChat hook by reloading the data
|
|
136
|
+
defaultCacheService.removeItem(`conversation_${conversationId}`);
|
|
137
|
+
options.onCleared?.();
|
|
138
|
+
} catch (error) {
|
|
139
|
+
console.error(
|
|
140
|
+
'[MessageHandlerService] Failed to clear conversation:',
|
|
141
|
+
error,
|
|
142
|
+
);
|
|
143
|
+
Alert.alert('Error', 'Failed to clear chat. Please try again.');
|
|
144
|
+
}
|
|
145
|
+
},
|
|
146
|
+
},
|
|
147
|
+
],
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Transform display messages with stable attachment mapping
|
|
153
|
+
*/
|
|
154
|
+
static transformDisplayMessages(
|
|
155
|
+
liveMessages: AppMessage[],
|
|
156
|
+
persistedMessages: AppMessage[],
|
|
157
|
+
lastUserAttachments?: AppAttachment[],
|
|
158
|
+
attachmentCache?: Record<string, AppAttachment[]>,
|
|
159
|
+
): AppMessage[] {
|
|
160
|
+
const persisted = new Map(persistedMessages.map((m) => [m.id, m]));
|
|
161
|
+
const lastUserIdx = [...liveMessages]
|
|
162
|
+
.map((m, i) => ({ m, i }))
|
|
163
|
+
.reverse()
|
|
164
|
+
.find((x) => x.m.role === 'user')?.i;
|
|
165
|
+
|
|
166
|
+
return liveMessages.map((m, i) => {
|
|
167
|
+
const fromDb = persisted.get(m.id);
|
|
168
|
+
const isMostRecentUser = m.role === 'user' && i === lastUserIdx;
|
|
169
|
+
// Attachment precedence:
|
|
170
|
+
// 1) Cached attachments for this live message ID (persist across renders)
|
|
171
|
+
// 2) Most recent user's optimistic attachments (for immediate display)
|
|
172
|
+
// 3) Attachments from persisted DB messages when IDs align
|
|
173
|
+
const fromCache = attachmentCache?.[m.id];
|
|
174
|
+
const attachments =
|
|
175
|
+
fromCache && fromCache.length
|
|
176
|
+
? fromCache
|
|
177
|
+
: ((isMostRecentUser && lastUserAttachments?.length
|
|
178
|
+
? lastUserAttachments
|
|
179
|
+
: fromDb?.attachments) ?? undefined);
|
|
180
|
+
|
|
181
|
+
return {
|
|
182
|
+
...m,
|
|
183
|
+
attachments,
|
|
184
|
+
toolCalls: m.toolCalls ?? fromDb?.toolCalls,
|
|
185
|
+
metadata: m.metadata ?? fromDb?.metadata,
|
|
186
|
+
};
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Chatbot Types
|
|
3
|
+
*
|
|
4
|
+
* Types for the chatbot feature using Supabase backend.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export type LocalMessageContentPart =
|
|
8
|
+
| { type: 'text'; text: string }
|
|
9
|
+
| { type: 'image'; image: string | URL };
|
|
10
|
+
|
|
11
|
+
export type MessageAttachment = {
|
|
12
|
+
type: 'image';
|
|
13
|
+
storageId: string;
|
|
14
|
+
url?: string;
|
|
15
|
+
fileName?: string;
|
|
16
|
+
mimeType?: string;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export type ToolCallRecord = {
|
|
20
|
+
toolName: string;
|
|
21
|
+
args: unknown;
|
|
22
|
+
result?: unknown;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export type MessageMetadata = {
|
|
26
|
+
streamId?: string;
|
|
27
|
+
status?: string;
|
|
28
|
+
usage?: {
|
|
29
|
+
promptTokens?: number;
|
|
30
|
+
completionTokens?: number;
|
|
31
|
+
totalTokens?: number;
|
|
32
|
+
};
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export type AgentStatus =
|
|
36
|
+
| {
|
|
37
|
+
type: 'thinking';
|
|
38
|
+
label?: string | null;
|
|
39
|
+
startedAt: number;
|
|
40
|
+
}
|
|
41
|
+
| {
|
|
42
|
+
type: 'tool';
|
|
43
|
+
toolName: string;
|
|
44
|
+
label?: string | null;
|
|
45
|
+
startedAt: number;
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
export type AppMessage = {
|
|
49
|
+
id: string;
|
|
50
|
+
role: 'user' | 'assistant' | 'data';
|
|
51
|
+
content: string | LocalMessageContentPart[];
|
|
52
|
+
createdAt?: Date;
|
|
53
|
+
parts?: unknown; // Optional hook into AI SDK shapes when present
|
|
54
|
+
attachments?: MessageAttachment[];
|
|
55
|
+
toolCalls?: ToolCallRecord[];
|
|
56
|
+
metadata?: MessageMetadata;
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
export type ConversationMessageFromDB = {
|
|
60
|
+
id: string;
|
|
61
|
+
created_at: string;
|
|
62
|
+
author_type: 'user' | 'bot';
|
|
63
|
+
text?: string | null;
|
|
64
|
+
attachments?: MessageAttachment[] | null;
|
|
65
|
+
tool_calls?: ToolCallRecord[] | null;
|
|
66
|
+
metadata?: MessageMetadata | null;
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
// Legacy type alias for backward compatibility
|
|
70
|
+
/** @deprecated Use MessageAttachment instead */
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import type { TelemetryMode } from '@/core/config';
|
|
2
|
+
import { getChatTelemetryMode } from '@/core/config/telemetry';
|
|
3
|
+
|
|
4
|
+
const DEFAULT_SAMPLE_PROBABILITY = 0.1;
|
|
5
|
+
|
|
6
|
+
type TimelineEntry = {
|
|
7
|
+
label: string;
|
|
8
|
+
msFromStart: number;
|
|
9
|
+
data?: Record<string, unknown>;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export type ChatTelemetryTracker = {
|
|
13
|
+
readonly event: string;
|
|
14
|
+
mark: (label: string, data?: Record<string, unknown>) => void;
|
|
15
|
+
finalize: (status: 'ok' | 'error', extra?: Record<string, unknown>) => void;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
function shouldTrack(mode: TelemetryMode, probability: number): boolean {
|
|
19
|
+
if (mode === 'off') {
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
if (mode === 'debug') {
|
|
23
|
+
return true;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const clampedProbability = Math.min(Math.max(probability, 0), 1);
|
|
27
|
+
return Math.random() < clampedProbability;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function createChatTelemetryTracker(
|
|
31
|
+
event: string,
|
|
32
|
+
baseContext: Record<string, unknown>,
|
|
33
|
+
sampleProbability = DEFAULT_SAMPLE_PROBABILITY,
|
|
34
|
+
): ChatTelemetryTracker | null {
|
|
35
|
+
const mode = getChatTelemetryMode();
|
|
36
|
+
if (!shouldTrack(mode, sampleProbability)) {
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const start = Date.now();
|
|
41
|
+
const timeline: TimelineEntry[] = [{ label: 'start', msFromStart: 0 }];
|
|
42
|
+
let finalized = false;
|
|
43
|
+
|
|
44
|
+
return {
|
|
45
|
+
event,
|
|
46
|
+
mark(label, data) {
|
|
47
|
+
if (finalized) {
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
const elapsed = Date.now() - start;
|
|
51
|
+
timeline.push({
|
|
52
|
+
label,
|
|
53
|
+
msFromStart: elapsed,
|
|
54
|
+
data,
|
|
55
|
+
});
|
|
56
|
+
},
|
|
57
|
+
finalize(status, extra) {
|
|
58
|
+
if (finalized) {
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
finalized = true;
|
|
62
|
+
const totalMs = Date.now() - start;
|
|
63
|
+
console.log('[chat][telemetry]', {
|
|
64
|
+
event,
|
|
65
|
+
mode,
|
|
66
|
+
status,
|
|
67
|
+
totalMs,
|
|
68
|
+
...baseContext,
|
|
69
|
+
...(extra ?? {}),
|
|
70
|
+
timeline,
|
|
71
|
+
});
|
|
72
|
+
},
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export function logChatTelemetryEvent(
|
|
77
|
+
event: string,
|
|
78
|
+
payload: Record<string, unknown>,
|
|
79
|
+
sampleProbability = DEFAULT_SAMPLE_PROBABILITY,
|
|
80
|
+
) {
|
|
81
|
+
const mode = getChatTelemetryMode();
|
|
82
|
+
if (!shouldTrack(mode, sampleProbability)) {
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
console.log('[chat][telemetry]', {
|
|
87
|
+
event,
|
|
88
|
+
mode,
|
|
89
|
+
...payload,
|
|
90
|
+
});
|
|
91
|
+
}
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Conversations Service
|
|
3
|
+
*
|
|
4
|
+
* Handles conversation operations including getOrCreateDefault, create, list, and delete.
|
|
5
|
+
*
|
|
6
|
+
* Requirements: 4.1, 4.2
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type { SupabaseClientType } from '../lib/supabase';
|
|
10
|
+
import type { Conversation } from '../types';
|
|
11
|
+
|
|
12
|
+
const DEFAULT_CONVERSATION_NAME = 'Default Chat';
|
|
13
|
+
|
|
14
|
+
export interface GetConversationResult {
|
|
15
|
+
success: boolean;
|
|
16
|
+
conversation?: Conversation;
|
|
17
|
+
error?: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface ListConversationsResult {
|
|
21
|
+
success: boolean;
|
|
22
|
+
conversations?: Conversation[];
|
|
23
|
+
error?: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface DeleteConversationResult {
|
|
27
|
+
success: boolean;
|
|
28
|
+
error?: string;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Get or create the default conversation for the current user
|
|
33
|
+
*
|
|
34
|
+
* This function returns an existing conversation named "Default Chat" or creates
|
|
35
|
+
* a new one if it doesn't exist. This ensures idempotency - calling multiple times
|
|
36
|
+
* always returns the same conversation.
|
|
37
|
+
*
|
|
38
|
+
* @param supabase - Supabase client (authenticated)
|
|
39
|
+
* @returns The default conversation
|
|
40
|
+
*
|
|
41
|
+
* Requirements: 4.1
|
|
42
|
+
*/
|
|
43
|
+
export async function getOrCreateDefault(
|
|
44
|
+
supabase: SupabaseClientType
|
|
45
|
+
): Promise<GetConversationResult> {
|
|
46
|
+
// Get the current auth user
|
|
47
|
+
const { data: authData, error: authError } = await supabase.auth.getUser();
|
|
48
|
+
|
|
49
|
+
if (authError || !authData.user) {
|
|
50
|
+
return { success: false, error: 'Not authenticated' };
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const userId = authData.user.id;
|
|
54
|
+
|
|
55
|
+
// Try to find existing default conversation (use limit 1 to handle duplicates)
|
|
56
|
+
const { data: existingList, error: findError } = await supabase
|
|
57
|
+
.from('conversations')
|
|
58
|
+
.select('*')
|
|
59
|
+
.eq('user_id', userId)
|
|
60
|
+
.eq('name', DEFAULT_CONVERSATION_NAME)
|
|
61
|
+
.order('created_at', { ascending: true })
|
|
62
|
+
.limit(1);
|
|
63
|
+
|
|
64
|
+
if (findError) {
|
|
65
|
+
return { success: false, error: `Failed to find conversation: ${findError.message}` };
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Return existing if found
|
|
69
|
+
if (existingList && existingList.length > 0) {
|
|
70
|
+
return { success: true, conversation: existingList[0] };
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Create new default conversation
|
|
74
|
+
const { data: created, error: createError } = await supabase
|
|
75
|
+
.from('conversations')
|
|
76
|
+
.insert({
|
|
77
|
+
user_id: userId,
|
|
78
|
+
name: DEFAULT_CONVERSATION_NAME,
|
|
79
|
+
})
|
|
80
|
+
.select()
|
|
81
|
+
.single();
|
|
82
|
+
|
|
83
|
+
if (createError) {
|
|
84
|
+
return { success: false, error: `Failed to create conversation: ${createError.message}` };
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return { success: true, conversation: created };
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Create a new conversation
|
|
93
|
+
*
|
|
94
|
+
* @param supabase - Supabase client (authenticated)
|
|
95
|
+
* @param name - Optional name for the conversation
|
|
96
|
+
* @returns The created conversation
|
|
97
|
+
*
|
|
98
|
+
* Requirements: 4.2
|
|
99
|
+
*/
|
|
100
|
+
export async function create(
|
|
101
|
+
supabase: SupabaseClientType,
|
|
102
|
+
name?: string
|
|
103
|
+
): Promise<GetConversationResult> {
|
|
104
|
+
// Get the current auth user
|
|
105
|
+
const { data: authData, error: authError } = await supabase.auth.getUser();
|
|
106
|
+
|
|
107
|
+
if (authError || !authData.user) {
|
|
108
|
+
return { success: false, error: 'Not authenticated' };
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const { data: conversation, error: createError } = await supabase
|
|
112
|
+
.from('conversations')
|
|
113
|
+
.insert({
|
|
114
|
+
user_id: authData.user.id,
|
|
115
|
+
name: name || null,
|
|
116
|
+
})
|
|
117
|
+
.select()
|
|
118
|
+
.single();
|
|
119
|
+
|
|
120
|
+
if (createError) {
|
|
121
|
+
return { success: false, error: `Failed to create conversation: ${createError.message}` };
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
return { success: true, conversation };
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* List all conversations for the current user
|
|
129
|
+
*
|
|
130
|
+
* @param supabase - Supabase client (authenticated)
|
|
131
|
+
* @returns List of conversations ordered by creation time (newest first)
|
|
132
|
+
*
|
|
133
|
+
* Requirements: 4.2
|
|
134
|
+
*/
|
|
135
|
+
export async function list(
|
|
136
|
+
supabase: SupabaseClientType
|
|
137
|
+
): Promise<ListConversationsResult> {
|
|
138
|
+
// Get the current auth user
|
|
139
|
+
const { data: authData, error: authError } = await supabase.auth.getUser();
|
|
140
|
+
|
|
141
|
+
if (authError || !authData.user) {
|
|
142
|
+
return { success: false, error: 'Not authenticated' };
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const { data: conversations, error: listError } = await supabase
|
|
146
|
+
.from('conversations')
|
|
147
|
+
.select('*')
|
|
148
|
+
.eq('user_id', authData.user.id)
|
|
149
|
+
.order('created_at', { ascending: false });
|
|
150
|
+
|
|
151
|
+
if (listError) {
|
|
152
|
+
return { success: false, error: `Failed to list conversations: ${listError.message}` };
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
return { success: true, conversations: conversations || [] };
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Delete a conversation
|
|
160
|
+
*
|
|
161
|
+
* This will also delete all messages in the conversation due to CASCADE.
|
|
162
|
+
*
|
|
163
|
+
* @param supabase - Supabase client (authenticated)
|
|
164
|
+
* @param conversationId - The ID of the conversation to delete
|
|
165
|
+
* @returns Success or error
|
|
166
|
+
*
|
|
167
|
+
* Requirements: 4.2
|
|
168
|
+
*/
|
|
169
|
+
export async function deleteConversation(
|
|
170
|
+
supabase: SupabaseClientType,
|
|
171
|
+
conversationId: string
|
|
172
|
+
): Promise<DeleteConversationResult> {
|
|
173
|
+
// Get the current auth user
|
|
174
|
+
const { data: authData, error: authError } = await supabase.auth.getUser();
|
|
175
|
+
|
|
176
|
+
if (authError || !authData.user) {
|
|
177
|
+
return { success: false, error: 'Not authenticated' };
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
if (!conversationId) {
|
|
181
|
+
return { success: false, error: 'Conversation ID is required' };
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// Delete the conversation (RLS ensures user can only delete their own)
|
|
185
|
+
const { error: deleteError } = await supabase
|
|
186
|
+
.from('conversations')
|
|
187
|
+
.delete()
|
|
188
|
+
.eq('id', conversationId)
|
|
189
|
+
.eq('user_id', authData.user.id);
|
|
190
|
+
|
|
191
|
+
if (deleteError) {
|
|
192
|
+
return { success: false, error: `Failed to delete conversation: ${deleteError.message}` };
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
return { success: true };
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Get a conversation by ID
|
|
200
|
+
*
|
|
201
|
+
* @param supabase - Supabase client (authenticated)
|
|
202
|
+
* @param conversationId - The ID of the conversation
|
|
203
|
+
* @returns The conversation or error
|
|
204
|
+
*/
|
|
205
|
+
export async function getById(
|
|
206
|
+
supabase: SupabaseClientType,
|
|
207
|
+
conversationId: string
|
|
208
|
+
): Promise<GetConversationResult> {
|
|
209
|
+
// Get the current auth user
|
|
210
|
+
const { data: authData, error: authError } = await supabase.auth.getUser();
|
|
211
|
+
|
|
212
|
+
if (authError || !authData.user) {
|
|
213
|
+
return { success: false, error: 'Not authenticated' };
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
if (!conversationId) {
|
|
217
|
+
return { success: false, error: 'Conversation ID is required' };
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
const { data: conversation, error: getError } = await supabase
|
|
221
|
+
.from('conversations')
|
|
222
|
+
.select('*')
|
|
223
|
+
.eq('id', conversationId)
|
|
224
|
+
.eq('user_id', authData.user.id)
|
|
225
|
+
.single();
|
|
226
|
+
|
|
227
|
+
if (getError) {
|
|
228
|
+
if (getError.code === 'PGRST116') {
|
|
229
|
+
return { success: false, error: 'Conversation not found' };
|
|
230
|
+
}
|
|
231
|
+
return { success: false, error: `Failed to get conversation: ${getError.message}` };
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
return { success: true, conversation };
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
export const conversationsService = {
|
|
238
|
+
getOrCreateDefault,
|
|
239
|
+
create,
|
|
240
|
+
list,
|
|
241
|
+
delete: deleteConversation,
|
|
242
|
+
getById,
|
|
243
|
+
};
|