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,429 @@
|
|
|
1
|
+
import React, { useCallback, useMemo, useState } from 'react';
|
|
2
|
+
import { Alert, Keyboard } from 'react-native';
|
|
3
|
+
|
|
4
|
+
import { chatbotApi } from '@/api-client';
|
|
5
|
+
import { getSupabase } from '@/api-client/supabase/client';
|
|
6
|
+
import { useToast } from '@/components/ui/utils';
|
|
7
|
+
import type { ReportReason } from '@/features/chatbot/constants/report-reasons';
|
|
8
|
+
import { useChatConfig } from '@/features/chatbot/hooks/use-chat-config';
|
|
9
|
+
import {
|
|
10
|
+
type AppAttachment,
|
|
11
|
+
MessageHandlerService,
|
|
12
|
+
} from '@/features/chatbot/services/message-handler-service';
|
|
13
|
+
import type { AppMessage } from '@/features/chatbot/types';
|
|
14
|
+
|
|
15
|
+
type UseChatHandlersOptions = {
|
|
16
|
+
conversationId: string | null;
|
|
17
|
+
initialMessages?: AppMessage[];
|
|
18
|
+
preferredProvider?: string;
|
|
19
|
+
preferredModel?: string;
|
|
20
|
+
searchMode?: string;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Hook to manage chat interactions, message handling, and reporting
|
|
25
|
+
*/
|
|
26
|
+
export const useChatHandlers = (options: UseChatHandlersOptions) => {
|
|
27
|
+
const {
|
|
28
|
+
conversationId,
|
|
29
|
+
initialMessages = [],
|
|
30
|
+
preferredProvider = 'openai',
|
|
31
|
+
preferredModel = 'gpt-5-nano',
|
|
32
|
+
searchMode = 'provider',
|
|
33
|
+
} = options;
|
|
34
|
+
|
|
35
|
+
// console.log('[CHATBOT_FRONTEND] 🎣 useChatHandlers hook 1:', {
|
|
36
|
+
// conversationId,
|
|
37
|
+
// hasAuthToken: !!authToken,
|
|
38
|
+
// initialMessagesCount: initialMessages.length,
|
|
39
|
+
// preferredProvider,
|
|
40
|
+
// preferredModel,
|
|
41
|
+
// searchMode,
|
|
42
|
+
// file: 'src/features/chatbot/hooks/use-chat-handlers.ts',
|
|
43
|
+
// function: 'useChatHandlers',
|
|
44
|
+
// });
|
|
45
|
+
|
|
46
|
+
const [messageToReport, setMessageToReport] = useState<AppMessage | null>(
|
|
47
|
+
null,
|
|
48
|
+
);
|
|
49
|
+
const [isPreflightSending, setIsPreflightSending] = useState(false);
|
|
50
|
+
const [signedUrlMap, setSignedUrlMap] = useState<Record<string, string>>({});
|
|
51
|
+
const lastUserAttachments = React.useRef<AppAttachment[] | undefined>(
|
|
52
|
+
undefined,
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
// Persist attachments per live message ID to avoid disappearing images
|
|
56
|
+
const attachmentCacheRef = React.useRef<Record<string, AppAttachment[]>>({});
|
|
57
|
+
|
|
58
|
+
const storeUserMessageMutation = chatbotApi.useStoreMessage();
|
|
59
|
+
const clearConversationMutation = chatbotApi.useClearConversation();
|
|
60
|
+
const deleteUserMessageMutation = chatbotApi.useDeleteUserMessage();
|
|
61
|
+
const toast = useToast();
|
|
62
|
+
const handleInputChangeRef = React.useRef<(text: string) => void>(() => {});
|
|
63
|
+
|
|
64
|
+
// Define onStreamFailure BEFORE passing it to useChatConfig
|
|
65
|
+
const onStreamFailure = useCallback(
|
|
66
|
+
async (details: {
|
|
67
|
+
userMessageId: string | null;
|
|
68
|
+
text: string;
|
|
69
|
+
attachments?: {
|
|
70
|
+
type: 'image';
|
|
71
|
+
storageId: string;
|
|
72
|
+
fileName?: string;
|
|
73
|
+
mimeType?: string;
|
|
74
|
+
}[];
|
|
75
|
+
errorMessage?: string;
|
|
76
|
+
}) => {
|
|
77
|
+
const message =
|
|
78
|
+
details.errorMessage ?? 'Message failed to send. Please try again.';
|
|
79
|
+
|
|
80
|
+
// Show error toast to user
|
|
81
|
+
toast.error(message);
|
|
82
|
+
|
|
83
|
+
// Restore user's input text so they can retry
|
|
84
|
+
handleInputChangeRef.current(details.text ?? '');
|
|
85
|
+
|
|
86
|
+
// Delete the failed user message from backend
|
|
87
|
+
if (details.userMessageId && conversationId) {
|
|
88
|
+
try {
|
|
89
|
+
await deleteUserMessageMutation.mutateAsync({
|
|
90
|
+
messageId: details.userMessageId,
|
|
91
|
+
conversationId,
|
|
92
|
+
});
|
|
93
|
+
} catch (error) {
|
|
94
|
+
console.warn('[useChatHandlers] Failed to remove failed message', {
|
|
95
|
+
messageId: details.userMessageId,
|
|
96
|
+
error,
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
delete attachmentCacheRef.current[details.userMessageId];
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Clear attachment cache
|
|
103
|
+
lastUserAttachments.current = undefined;
|
|
104
|
+
},
|
|
105
|
+
[conversationId, deleteUserMessageMutation, toast],
|
|
106
|
+
);
|
|
107
|
+
|
|
108
|
+
const chatConfig = useChatConfig({
|
|
109
|
+
conversationId,
|
|
110
|
+
initialMessages,
|
|
111
|
+
preferredProvider,
|
|
112
|
+
preferredModel,
|
|
113
|
+
searchMode,
|
|
114
|
+
onStreamFailure,
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
handleInputChangeRef.current = chatConfig.handleInputChange;
|
|
118
|
+
|
|
119
|
+
const displayMessages = useMemo((): AppMessage[] => {
|
|
120
|
+
return MessageHandlerService.transformDisplayMessages(
|
|
121
|
+
chatConfig.messages,
|
|
122
|
+
initialMessages,
|
|
123
|
+
lastUserAttachments.current,
|
|
124
|
+
attachmentCacheRef.current,
|
|
125
|
+
) as AppMessage[];
|
|
126
|
+
}, [initialMessages, chatConfig.messages]);
|
|
127
|
+
|
|
128
|
+
// Fetch signed URLs for any attachments missing urls (e.g., historical messages)
|
|
129
|
+
React.useEffect(() => {
|
|
130
|
+
const supabase = getSupabase();
|
|
131
|
+
const missing = new Set<string>();
|
|
132
|
+
|
|
133
|
+
for (const msg of displayMessages) {
|
|
134
|
+
if (!msg.attachments?.length) continue;
|
|
135
|
+
for (const att of msg.attachments) {
|
|
136
|
+
if (att.type !== 'image') continue;
|
|
137
|
+
if (att.url || signedUrlMap[att.storageId]) continue;
|
|
138
|
+
missing.add(att.storageId);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
if (missing.size === 0) return;
|
|
143
|
+
let cancelled = false;
|
|
144
|
+
|
|
145
|
+
(async () => {
|
|
146
|
+
const updates: Record<string, string> = {};
|
|
147
|
+
for (const storageId of missing) {
|
|
148
|
+
const { data, error } = await supabase.storage
|
|
149
|
+
.from('chat-attachments')
|
|
150
|
+
.createSignedUrl(storageId, 60 * 60);
|
|
151
|
+
if (error) {
|
|
152
|
+
console.warn('[useChatHandlers] Failed to sign attachment', {
|
|
153
|
+
storageId,
|
|
154
|
+
error: error.message,
|
|
155
|
+
});
|
|
156
|
+
continue;
|
|
157
|
+
}
|
|
158
|
+
if (data?.signedUrl) {
|
|
159
|
+
updates[storageId] = data.signedUrl;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
if (!cancelled && Object.keys(updates).length > 0) {
|
|
164
|
+
setSignedUrlMap((prev) => ({ ...prev, ...updates }));
|
|
165
|
+
}
|
|
166
|
+
})();
|
|
167
|
+
|
|
168
|
+
return () => {
|
|
169
|
+
cancelled = true;
|
|
170
|
+
};
|
|
171
|
+
}, [displayMessages, signedUrlMap]);
|
|
172
|
+
|
|
173
|
+
const messagesWithSignedUrls = useMemo((): AppMessage[] => {
|
|
174
|
+
if (!Object.keys(signedUrlMap).length) return displayMessages;
|
|
175
|
+
return displayMessages.map((msg) => {
|
|
176
|
+
if (!msg.attachments?.length) return msg;
|
|
177
|
+
return {
|
|
178
|
+
...msg,
|
|
179
|
+
attachments: msg.attachments.map((att) =>
|
|
180
|
+
att.type === 'image'
|
|
181
|
+
? { ...att, url: att.url ?? signedUrlMap[att.storageId] }
|
|
182
|
+
: att,
|
|
183
|
+
),
|
|
184
|
+
};
|
|
185
|
+
});
|
|
186
|
+
}, [displayMessages, signedUrlMap]);
|
|
187
|
+
|
|
188
|
+
// After we compute display messages, cache any attachments by live message ID
|
|
189
|
+
React.useEffect(() => {
|
|
190
|
+
if (!displayMessages?.length) return;
|
|
191
|
+
const cache = attachmentCacheRef.current;
|
|
192
|
+
let changed = false;
|
|
193
|
+
for (const m of displayMessages) {
|
|
194
|
+
if (m.attachments && m.attachments.length) {
|
|
195
|
+
const existing = cache[m.id];
|
|
196
|
+
if (!existing || existing.length !== m.attachments.length) {
|
|
197
|
+
cache[m.id] = m.attachments as AppAttachment[];
|
|
198
|
+
changed = true;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
if (changed) {
|
|
203
|
+
// noop: we mutate ref; next memo pass will read updated cache
|
|
204
|
+
}
|
|
205
|
+
}, [displayMessages]);
|
|
206
|
+
|
|
207
|
+
// useEffect(() => {
|
|
208
|
+
// console.log(
|
|
209
|
+
// '[Client useChatHandlers] DisplayMessages updated count:',
|
|
210
|
+
// displayMessages.length
|
|
211
|
+
// );
|
|
212
|
+
// if (displayMessages.length > 0) {
|
|
213
|
+
// const lastMsg = displayMessages[displayMessages.length - 1];
|
|
214
|
+
// if (lastMsg) {
|
|
215
|
+
// console.log(
|
|
216
|
+
// `[Client useChatHandlers] Last display message (ID: ${lastMsg.id}, Role: ${lastMsg.role}, Content Type: ${typeof lastMsg.content}):`,
|
|
217
|
+
// JSON.stringify(lastMsg.content)
|
|
218
|
+
// );
|
|
219
|
+
// }
|
|
220
|
+
// }
|
|
221
|
+
// }, [displayMessages]);
|
|
222
|
+
|
|
223
|
+
const handleReportMessage = useCallback((message: AppMessage) => {
|
|
224
|
+
setMessageToReport(message);
|
|
225
|
+
}, []);
|
|
226
|
+
|
|
227
|
+
const handleSubmitReport = useCallback(
|
|
228
|
+
async (reportData: {
|
|
229
|
+
messageId?: string;
|
|
230
|
+
reason: ReportReason;
|
|
231
|
+
details?: string;
|
|
232
|
+
}): Promise<boolean> => {
|
|
233
|
+
if (!reportData.messageId) {
|
|
234
|
+
console.warn(
|
|
235
|
+
'[Client useChatHandlers] Report dismissed: missing messageId',
|
|
236
|
+
);
|
|
237
|
+
return false;
|
|
238
|
+
}
|
|
239
|
+
try {
|
|
240
|
+
// TODO: Implement reporting via Supabase when needed
|
|
241
|
+
console.log('[Client useChatHandlers] Report submitted:', reportData);
|
|
242
|
+
Alert.alert('Report Submitted', 'Thank you for your report.', [
|
|
243
|
+
{ text: 'OK' },
|
|
244
|
+
]);
|
|
245
|
+
return true;
|
|
246
|
+
} catch (error) {
|
|
247
|
+
console.error(
|
|
248
|
+
'[Client useChatHandlers] Failed to submit report:',
|
|
249
|
+
error,
|
|
250
|
+
);
|
|
251
|
+
Alert.alert(
|
|
252
|
+
'Report Failed',
|
|
253
|
+
"We couldn't submit your report. Please try again.",
|
|
254
|
+
[{ text: 'OK' }],
|
|
255
|
+
);
|
|
256
|
+
return false;
|
|
257
|
+
}
|
|
258
|
+
},
|
|
259
|
+
[],
|
|
260
|
+
);
|
|
261
|
+
|
|
262
|
+
const handleCancelReport = useCallback(() => {
|
|
263
|
+
setMessageToReport(null);
|
|
264
|
+
}, []);
|
|
265
|
+
|
|
266
|
+
const handleTextInputChange = useCallback(
|
|
267
|
+
(text: string) => {
|
|
268
|
+
chatConfig.handleInputChange(text);
|
|
269
|
+
},
|
|
270
|
+
[chatConfig],
|
|
271
|
+
);
|
|
272
|
+
|
|
273
|
+
const handleMessageSubmit = useCallback(
|
|
274
|
+
async (
|
|
275
|
+
attachmentsFromInputBar?: {
|
|
276
|
+
type: 'image';
|
|
277
|
+
storageId: string;
|
|
278
|
+
fileName?: string;
|
|
279
|
+
mimeType?: string;
|
|
280
|
+
url?: string;
|
|
281
|
+
}[],
|
|
282
|
+
) => {
|
|
283
|
+
// Dismiss keyboard when sending a message
|
|
284
|
+
Keyboard.dismiss();
|
|
285
|
+
|
|
286
|
+
// Read the current input value just before submission to prevent stale closure bugs
|
|
287
|
+
const currentInput = chatConfig.input;
|
|
288
|
+
const trimmedInput = currentInput.trim();
|
|
289
|
+
|
|
290
|
+
// console.log(
|
|
291
|
+
// '[Client useChatHandlers handleMessageSubmit] Attempting to submit. Input:',
|
|
292
|
+
// currentInput,
|
|
293
|
+
// 'Attachments from InputBar:',
|
|
294
|
+
// attachmentsFromInputBar,
|
|
295
|
+
// 'ConvID:',
|
|
296
|
+
// conversationId
|
|
297
|
+
// );
|
|
298
|
+
|
|
299
|
+
if (!conversationId) {
|
|
300
|
+
console.error(
|
|
301
|
+
'[Client useChatHandlers handleMessageSubmit] Missing conversationId.',
|
|
302
|
+
);
|
|
303
|
+
Alert.alert(
|
|
304
|
+
'Error',
|
|
305
|
+
'Chat session not fully initialized. Cannot send message.',
|
|
306
|
+
);
|
|
307
|
+
return;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
if (
|
|
311
|
+
!trimmedInput &&
|
|
312
|
+
(!attachmentsFromInputBar || attachmentsFromInputBar.length === 0)
|
|
313
|
+
) {
|
|
314
|
+
console.log(
|
|
315
|
+
'[Client useChatHandlers handleMessageSubmit] Empty message and no attachments. Not submitting.',
|
|
316
|
+
);
|
|
317
|
+
return;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
// Clear the input immediately and mark preflight sending so UI responds fast
|
|
321
|
+
setIsPreflightSending(true);
|
|
322
|
+
handleInputChangeRef.current('');
|
|
323
|
+
|
|
324
|
+
// Store attachments in ref for optimistic UI display
|
|
325
|
+
lastUserAttachments.current = attachmentsFromInputBar?.map((att) => ({
|
|
326
|
+
type: 'image' as const,
|
|
327
|
+
storageId: att.storageId,
|
|
328
|
+
fileName: att.fileName,
|
|
329
|
+
mimeType: att.mimeType,
|
|
330
|
+
url: att.url,
|
|
331
|
+
}));
|
|
332
|
+
|
|
333
|
+
// Store user message and retrieve the persisted ID before triggering streaming
|
|
334
|
+
const storedMessageId = await MessageHandlerService.storeUserMessage({
|
|
335
|
+
currentInput: trimmedInput,
|
|
336
|
+
conversationId,
|
|
337
|
+
attachmentsFromInputBar,
|
|
338
|
+
storeUserMessageMutation,
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
if (!storedMessageId) {
|
|
342
|
+
console.error(
|
|
343
|
+
'[Client useChatHandlers] Unable to store user message; aborting streaming request.',
|
|
344
|
+
);
|
|
345
|
+
setIsPreflightSending(false);
|
|
346
|
+
handleInputChangeRef.current(currentInput);
|
|
347
|
+
lastUserAttachments.current = undefined;
|
|
348
|
+
return;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
// Submit to AI
|
|
352
|
+
const dummyFormEvent = {
|
|
353
|
+
preventDefault: () => {},
|
|
354
|
+
} as React.FormEvent<HTMLFormElement>;
|
|
355
|
+
|
|
356
|
+
try {
|
|
357
|
+
const submitPromise = chatConfig.handleSubmit(dummyFormEvent, {
|
|
358
|
+
body: {
|
|
359
|
+
conversationId: conversationId,
|
|
360
|
+
preferredModel,
|
|
361
|
+
userMessageId: storedMessageId,
|
|
362
|
+
text: trimmedInput,
|
|
363
|
+
attachments:
|
|
364
|
+
attachmentsFromInputBar && attachmentsFromInputBar.length > 0
|
|
365
|
+
? attachmentsFromInputBar
|
|
366
|
+
: undefined,
|
|
367
|
+
},
|
|
368
|
+
});
|
|
369
|
+
// Handover loading state to chatConfig once stream starts
|
|
370
|
+
setIsPreflightSending(false);
|
|
371
|
+
await submitPromise;
|
|
372
|
+
} catch (submitError) {
|
|
373
|
+
console.error(
|
|
374
|
+
'[Client useChatHandlers] Streaming submission failed:',
|
|
375
|
+
submitError,
|
|
376
|
+
);
|
|
377
|
+
setIsPreflightSending(false);
|
|
378
|
+
handleInputChangeRef.current(currentInput);
|
|
379
|
+
return;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
// console.log(
|
|
383
|
+
// '[Client useChatHandlers handleMessageSubmit] chatConfig.handleSubmit called for bot response.'
|
|
384
|
+
// );
|
|
385
|
+
|
|
386
|
+
// Note: We don't clear lastUserAttachments here anymore.
|
|
387
|
+
// Once initialMessages contains the stored user message (with attachments),
|
|
388
|
+
// the initialEquivalent?.attachments path takes over naturally.
|
|
389
|
+
},
|
|
390
|
+
[conversationId, storeUserMessageMutation, chatConfig, preferredModel],
|
|
391
|
+
);
|
|
392
|
+
|
|
393
|
+
const handleClearChat = useCallback(() => {
|
|
394
|
+
if (!conversationId) {
|
|
395
|
+
console.error('[handleClearChat] Missing conversationId');
|
|
396
|
+
return;
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
MessageHandlerService.clearChat({
|
|
400
|
+
conversationId,
|
|
401
|
+
clearConversationMutation,
|
|
402
|
+
onCleared: () => {
|
|
403
|
+
chatConfig.resetMessages();
|
|
404
|
+
},
|
|
405
|
+
});
|
|
406
|
+
// Reset local caches immediately for a clean slate
|
|
407
|
+
lastUserAttachments.current = undefined;
|
|
408
|
+
attachmentCacheRef.current = {};
|
|
409
|
+
}, [conversationId, clearConversationMutation, chatConfig]);
|
|
410
|
+
|
|
411
|
+
// When conversation changes, reset transient caches to prevent leakage
|
|
412
|
+
React.useEffect(() => {
|
|
413
|
+
lastUserAttachments.current = undefined;
|
|
414
|
+
attachmentCacheRef.current = {};
|
|
415
|
+
}, [conversationId]);
|
|
416
|
+
|
|
417
|
+
return {
|
|
418
|
+
...chatConfig,
|
|
419
|
+
isLoading: chatConfig.isLoading || isPreflightSending,
|
|
420
|
+
messages: messagesWithSignedUrls,
|
|
421
|
+
messageToReport,
|
|
422
|
+
handleReportMessage,
|
|
423
|
+
handleSubmitReport,
|
|
424
|
+
handleCancelReport,
|
|
425
|
+
handleTextInputChange,
|
|
426
|
+
handleMessageSubmit,
|
|
427
|
+
handleClearChat,
|
|
428
|
+
};
|
|
429
|
+
};
|
package/recipes/chatbot-supabase/apps/native/src/features/chatbot/hooks/use-chatbot-settings.ts
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { useCallback, useEffect, useState } from 'react';
|
|
2
|
+
|
|
3
|
+
import { defaultCacheService } from '@/core/cache';
|
|
4
|
+
|
|
5
|
+
import type { Provider } from '../constants/models';
|
|
6
|
+
|
|
7
|
+
export type SearchMode = 'provider' | 'tavily' | 'none';
|
|
8
|
+
|
|
9
|
+
type ChatbotSettings = {
|
|
10
|
+
provider: Provider;
|
|
11
|
+
model: string;
|
|
12
|
+
searchMode: SearchMode;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
const CACHE_KEY = 'chatbot_settings';
|
|
16
|
+
|
|
17
|
+
const DEFAULT_SETTINGS: ChatbotSettings = {
|
|
18
|
+
provider: 'openai',
|
|
19
|
+
model: 'gpt-4.1-nano',
|
|
20
|
+
searchMode: 'tavily',
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export function useChatbotSettings() {
|
|
24
|
+
const [settings, setSettings] = useState<ChatbotSettings>(DEFAULT_SETTINGS);
|
|
25
|
+
const [isLoaded, setIsLoaded] = useState(false);
|
|
26
|
+
|
|
27
|
+
// Load settings from cache on mount
|
|
28
|
+
useEffect(() => {
|
|
29
|
+
const loadSettings = async () => {
|
|
30
|
+
try {
|
|
31
|
+
const cachedSettings =
|
|
32
|
+
await defaultCacheService.getItem<ChatbotSettings>(CACHE_KEY);
|
|
33
|
+
if (cachedSettings) {
|
|
34
|
+
setSettings(cachedSettings);
|
|
35
|
+
}
|
|
36
|
+
} catch (error) {
|
|
37
|
+
console.error('[useChatbotSettings] Failed to load settings:', error);
|
|
38
|
+
} finally {
|
|
39
|
+
setIsLoaded(true);
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
loadSettings();
|
|
44
|
+
}, []);
|
|
45
|
+
|
|
46
|
+
// Save settings to cache whenever they change
|
|
47
|
+
const updateSettings = useCallback(
|
|
48
|
+
async (newSettings: Partial<ChatbotSettings>) => {
|
|
49
|
+
const updatedSettings = { ...settings, ...newSettings };
|
|
50
|
+
setSettings(updatedSettings);
|
|
51
|
+
|
|
52
|
+
try {
|
|
53
|
+
await defaultCacheService.setItem(CACHE_KEY, updatedSettings);
|
|
54
|
+
} catch (error) {
|
|
55
|
+
console.error('[useChatbotSettings] Failed to save settings:', error);
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
[settings],
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
const updateProvider = useCallback(
|
|
62
|
+
(provider: Provider) => {
|
|
63
|
+
updateSettings({ provider });
|
|
64
|
+
},
|
|
65
|
+
[updateSettings],
|
|
66
|
+
);
|
|
67
|
+
|
|
68
|
+
const updateModel = useCallback(
|
|
69
|
+
(model: string) => {
|
|
70
|
+
updateSettings({ model });
|
|
71
|
+
},
|
|
72
|
+
[updateSettings],
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
const updateSearchMode = useCallback(
|
|
76
|
+
(searchMode: SearchMode) => {
|
|
77
|
+
updateSettings({ searchMode });
|
|
78
|
+
},
|
|
79
|
+
[updateSettings],
|
|
80
|
+
);
|
|
81
|
+
|
|
82
|
+
return {
|
|
83
|
+
settings,
|
|
84
|
+
isLoaded,
|
|
85
|
+
updateProvider,
|
|
86
|
+
updateModel,
|
|
87
|
+
updateSearchMode,
|
|
88
|
+
};
|
|
89
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { useEffect, useMemo, useRef, useState } from 'react';
|
|
2
|
+
|
|
3
|
+
import { chatbotApi } from '@/api-client';
|
|
4
|
+
import type {
|
|
5
|
+
AppMessage,
|
|
6
|
+
MessageAttachment,
|
|
7
|
+
MessageMetadata,
|
|
8
|
+
ToolCallRecord,
|
|
9
|
+
} from '@/features/chatbot/types';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Hook to manage conversation state and load historical messages
|
|
13
|
+
*/
|
|
14
|
+
export const useConversation = (userId?: string) => {
|
|
15
|
+
const getOrCreateConversation = chatbotApi.useGetOrCreateConversation();
|
|
16
|
+
const [conversationId, setConversationId] = useState<string | null>(null);
|
|
17
|
+
const lastUserIdRef = useRef<string | undefined>(undefined);
|
|
18
|
+
const initializingRef = useRef(false);
|
|
19
|
+
|
|
20
|
+
useEffect(() => {
|
|
21
|
+
// Reset if user changed
|
|
22
|
+
if (userId !== lastUserIdRef.current) {
|
|
23
|
+
lastUserIdRef.current = userId;
|
|
24
|
+
initializingRef.current = false;
|
|
25
|
+
setConversationId(null);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const initializeConversation = async () => {
|
|
29
|
+
// Guard: skip if no user, already have conversation, or already initializing
|
|
30
|
+
if (!userId || conversationId || initializingRef.current) {
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
initializingRef.current = true;
|
|
35
|
+
console.log(
|
|
36
|
+
'[Client useConversation] Initializing conversation for user:',
|
|
37
|
+
userId,
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
try {
|
|
41
|
+
const conversation = await getOrCreateConversation.mutateAsync();
|
|
42
|
+
if (conversation?.id) {
|
|
43
|
+
setConversationId(conversation.id);
|
|
44
|
+
console.log(
|
|
45
|
+
'[Client useConversation] Conversation ID set:',
|
|
46
|
+
conversation.id,
|
|
47
|
+
);
|
|
48
|
+
} else {
|
|
49
|
+
// No conversation returned, allow retry
|
|
50
|
+
initializingRef.current = false;
|
|
51
|
+
}
|
|
52
|
+
} catch (error) {
|
|
53
|
+
console.error(
|
|
54
|
+
'[Client useConversation] Failed to get/create conversation:',
|
|
55
|
+
error,
|
|
56
|
+
);
|
|
57
|
+
initializingRef.current = false;
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
initializeConversation();
|
|
62
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
63
|
+
}, [userId, conversationId]);
|
|
64
|
+
|
|
65
|
+
const historicalMessagesResult = chatbotApi.useListMessages(conversationId);
|
|
66
|
+
|
|
67
|
+
const initialMessages = useMemo((): AppMessage[] => {
|
|
68
|
+
const pages = historicalMessagesResult.data?.pages;
|
|
69
|
+
if (!pages || pages.length === 0) {
|
|
70
|
+
return [];
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Flatten all pages and get messages
|
|
74
|
+
const allMessages = pages.flatMap((page) => page?.messages || []);
|
|
75
|
+
|
|
76
|
+
return allMessages.map(
|
|
77
|
+
(msg): AppMessage => ({
|
|
78
|
+
id: msg.id,
|
|
79
|
+
role: msg.author_type === 'user' ? 'user' : 'assistant',
|
|
80
|
+
content: msg.text || '',
|
|
81
|
+
createdAt: new Date(msg.created_at),
|
|
82
|
+
attachments: msg.attachments as MessageAttachment[] | undefined,
|
|
83
|
+
toolCalls: msg.tool_calls as ToolCallRecord[] | undefined,
|
|
84
|
+
metadata: msg.metadata as MessageMetadata | undefined,
|
|
85
|
+
}),
|
|
86
|
+
);
|
|
87
|
+
}, [historicalMessagesResult.data]);
|
|
88
|
+
|
|
89
|
+
return { conversationId, initialMessages };
|
|
90
|
+
};
|